summaryrefslogtreecommitdiff
path: root/core/src/main/java/org/elasticsearch/common/unit/DistanceUnit.java
blob: d0e91646c0fa67f511b4ee43f69229cb3a34b7d0 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
/*
 * Licensed to Elasticsearch under one or more contributor
 * license agreements. See the NOTICE file distributed with
 * this work for additional information regarding copyright
 * ownership. Elasticsearch licenses this file to you under
 * the Apache License, Version 2.0 (the "License"); you may
 * not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *    http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 */

package org.elasticsearch.common.unit;

import org.elasticsearch.common.geo.GeoUtils;
import org.elasticsearch.common.io.stream.StreamInput;
import org.elasticsearch.common.io.stream.StreamOutput;
import org.elasticsearch.common.io.stream.Writeable;

import java.io.IOException;

/**
 * The DistanceUnit enumerates several units for measuring distances. These units 
 * provide methods for converting strings and methods to convert units among each
 * others. Some methods like {@link DistanceUnit#getEarthCircumference} refer to
 * the earth ellipsoid defined in {@link GeoUtils}. The default unit used within
 * this project is <code>METERS</code> which is defined by <code>DEFAULT</code>
 */
public enum DistanceUnit implements Writeable<DistanceUnit> {
    INCH(0.0254, "in", "inch"),
    YARD(0.9144, "yd", "yards"),
    FEET(0.3048, "ft", "feet"),
    KILOMETERS(1000.0, "km", "kilometers"),
    NAUTICALMILES(1852.0, "NM", "nmi", "nauticalmiles"),
    MILLIMETERS(0.001, "mm", "millimeters"),
    CENTIMETERS(0.01, "cm", "centimeters"),

    // 'm' is a suffix of 'nmi' so it must follow 'nmi'
    MILES(1609.344, "mi", "miles"),

    // since 'm' is suffix of other unit
    // it must be the last entry of unit
    // names ending with 'm'. otherwise
    // parsing would fail
    METERS(1, "m", "meters");

    public static final DistanceUnit DEFAULT = METERS;

    private double meters; 
    private final String[] names;

    DistanceUnit(double meters, String...names) {
        this.meters = meters;
        this.names = names;
    }

    /**
     * Measures the circumference of earth in this unit
     * 
     * @return length of earth circumference in this unit
     */
    public double getEarthCircumference() {
        return GeoUtils.EARTH_EQUATOR / meters;
    }

    /**
     * Measures the radius of earth in this unit
     * 
     * @return length of earth radius in this unit
     */
    public double getEarthRadius() {
        return GeoUtils.EARTH_SEMI_MAJOR_AXIS / meters;
    }

    /**
     * Measures a longitude in this unit
     * 
     * @return length of a longitude degree in this unit
     */
    public double getDistancePerDegree() {
        return GeoUtils.EARTH_EQUATOR / (360.0 * meters);
    }

    /**
     * Convert a value into meters
     * 
     * @param distance distance in this unit
     * @return value in meters
     */
    public double toMeters(double distance) {
        return convert(distance, this, DistanceUnit.METERS);
    }

    /**
     * Convert a value given in meters to a value of this unit
     * 
     * @param distance distance in meters
     * @return value in this unit
     */
    public double fromMeters(double distance) {
        return convert(distance, DistanceUnit.METERS, this);
    }

    /** 
     * Convert a given value into another unit
     * 
     * @param distance value in this unit
     * @param unit source unit
     * @return value in this unit
     */
    public double convert(double distance, DistanceUnit unit) {
        return convert(distance, unit, this);
    }

    /**
     * Convert a value to a distance string
     * 
     * @param distance value to convert
     * @return String representation of the distance 
     */
    public String toString(double distance) {
        return distance + toString();
    }

    @Override
    public String toString() {
        return names[0];
    }

    /**
     * Converts the given distance from the given DistanceUnit, to the given DistanceUnit
     *
     * @param distance Distance to convert
     * @param from     Unit to convert the distance from
     * @param to       Unit of distance to convert to
     * @return Given distance converted to the distance in the given unit
     */
    public static double convert(double distance, DistanceUnit from, DistanceUnit to) {
        if (from == to) {
            return distance;
        } else {
            return distance * from.meters / to.meters;
        }
    }

    /**
     * Parses a given distance and converts it to the specified unit.
     * 
     * @param distance String defining a distance (value and unit)
     * @param defaultUnit unit assumed if none is defined
     * @param to unit of result
     * @return parsed distance
     */
    public static double parse(String distance, DistanceUnit defaultUnit, DistanceUnit to) {
        Distance dist = Distance.parseDistance(distance, defaultUnit);
        return convert(dist.value, dist.unit, to);
    }

    /**
     * Parses a given distance and converts it to this unit.
     * 
     * @param distance String defining a distance (value and unit)
     * @param defaultUnit unit to expect if none if provided
     * @return parsed distance
     */
    public double parse(String distance, DistanceUnit defaultUnit) {
        return parse(distance, defaultUnit, this);
    }

    /**
     * Convert a String to a {@link DistanceUnit}
     * 
     * @param unit name of the unit
     * @return unit matching the given name
     * @throws IllegalArgumentException if no unit matches the given name
     */
    public static DistanceUnit fromString(String unit) {
        for (DistanceUnit dunit : values()) {
            for (String name : dunit.names) {
                if(name.equals(unit)) {
                    return dunit;
                }
            }
        }
        throw new IllegalArgumentException("No distance unit match [" + unit + "]");
    }

    /**
     * Parses the suffix of a given distance string and return the corresponding {@link DistanceUnit}
     * 
     * @param distance string representing a distance
     * @param defaultUnit default unit to use, if no unit is provided by the string
     * @return unit of the given distance
     */
    public static DistanceUnit parseUnit(String distance, DistanceUnit defaultUnit) {
        for (DistanceUnit unit : values()) {
            for (String name : unit.names) {
                if(distance.endsWith(name)) {
                    return unit;
                }
            }
        }
        return defaultUnit;
    }

    /**
     * Write a {@link DistanceUnit} to a {@link StreamOutput}
     * 
     * @param out {@link StreamOutput} to write to
     * @param unit {@link DistanceUnit} to write 
     */
    public static void writeDistanceUnit(StreamOutput out, DistanceUnit unit) throws IOException {
        out.writeByte((byte) unit.ordinal());
    }

    /**
     * Read a {@link DistanceUnit} from a {@link StreamInput} 
     * 
     * @param in {@link StreamInput} to read the {@link DistanceUnit} from
     * @return {@link DistanceUnit} read from the {@link StreamInput}
     * @throws IOException if no unit can be read from the {@link StreamInput}
     * @throws IllegalArgumentException if no matching {@link DistanceUnit} can be found
     */
    public static DistanceUnit readDistanceUnit(StreamInput in) throws IOException {
        byte b = in.readByte();

        if(b<0 || b>=values().length) {
            throw new IllegalArgumentException("No type for distance unit matching [" + b + "]");
        } else {
            return values()[b];
        }
    }

    /**
     * This class implements a value+unit tuple.
     */
    public static class Distance implements Comparable<Distance> {
        public final double value;
        public final DistanceUnit unit;

        public Distance(double value, DistanceUnit unit) {
            super();
            this.value = value;
            this.unit = unit;
        }

        /**
         * Converts a {@link Distance} value given in a specific {@link DistanceUnit} into
         * a value equal to the specified value but in a other {@link DistanceUnit}.
         * 
         * @param unit unit of the result
         * @return converted distance
         */
        public Distance convert(DistanceUnit unit) {
            if(this.unit == unit) {
                return this;
            } else {
                return new Distance(DistanceUnit.convert(value, this.unit, unit), unit);
            }
        }

        @Override
        public boolean equals(Object obj) {
            if(obj == null) {
                return false;
            } else if (obj instanceof Distance) {
                Distance other = (Distance) obj;
                return DistanceUnit.convert(value, unit, other.unit) == other.value;
            } else {
                return false;
            }
        }

        @Override
        public int hashCode() {
            return Double.valueOf(value * unit.meters).hashCode();
        }

        @Override
        public int compareTo(Distance o) {
            return Double.compare(value, DistanceUnit.convert(o.value, o.unit, unit));
        }

        @Override
        public String toString() {
            return unit.toString(value);
        }

        /**
         * Parse a {@link Distance} from a given String. If no unit is given
         * <code>DistanceUnit.DEFAULT</code> will be used 
         * 
         * @param distance String defining a {@link Distance} 
         * @return parsed {@link Distance}
         */
        public static Distance parseDistance(String distance) {
            return parseDistance(distance, DEFAULT);
        }

        /**
         * Parse a {@link Distance} from a given String
         * 
         * @param distance String defining a {@link Distance} 
         * @param defaultUnit {@link DistanceUnit} to be assumed
         *          if not unit is provided in the first argument  
         * @return parsed {@link Distance}
         */
        private static Distance parseDistance(String distance, DistanceUnit defaultUnit) {
            for (DistanceUnit unit : values()) {
                for (String name : unit.names) {
                    if(distance.endsWith(name)) {
                        return new Distance(Double.parseDouble(distance.substring(0, distance.length() - name.length())), unit);
                    }
                }
            }
            return new Distance(Double.parseDouble(distance), defaultUnit);
        }
    }

    private static final DistanceUnit PROTOTYPE = DEFAULT;

    @Override
    public DistanceUnit readFrom(StreamInput in) throws IOException {
        int ordinal = in.readVInt();
        if (ordinal < 0 || ordinal >= values().length) {
            throw new IOException("Unknown DistanceUnit ordinal [" + ordinal + "]");
        }
        return values()[ordinal];
    }

    public static DistanceUnit readUnitFrom(StreamInput in) throws IOException {
        return PROTOTYPE.readFrom(in);
    }

    @Override
    public void writeTo(StreamOutput out) throws IOException {
        out.writeVInt(this.ordinal());
    }
}