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
347
348
349
350
351
352
353
354
355
/*! Mapping for Elasticsearch `geo_shape` types. */

use geo::mapping::Distance;
use serde::{
    Serialize,
    Serializer,
};

/** A field that will be mapped as a `geo_shape`. */
pub trait GeoShapeFieldType<TMapping> {}

/**
The base requirements for mapping a `geo_shape` type.

Custom mappings can be defined by implementing `GeoShapeMapping`.

# Examples

Define a custom `GeoShapeMapping`:

```
# #[macro_use]
# extern crate elastic_types;
# extern crate serde;
# use elastic_types::prelude::*;
#[derive(Default)]
struct MyGeoShapeMapping;
impl GeoShapeMapping for MyGeoShapeMapping {
    //Overload the mapping functions here
    fn tree_levels() -> Option<i32> {
        Some(2)
    }
}
# fn main() {}
```

This will produce the following mapping:

```
# #[macro_use]
# extern crate json_str;
# #[macro_use]
# extern crate elastic_types;
# extern crate serde;
# extern crate serde_json;
# use elastic_types::prelude::*;
#[derive(Default)]
# struct MyGeoShapeMapping;
# impl GeoShapeMapping for MyGeoShapeMapping {
#     //Overload the mapping functions here
#     fn tree_levels() -> Option<i32> {
#         Some(2)
#     }
# }
# fn main() {
# let mapping = elastic_types::derive::standalone_field_ser(MyGeoShapeMapping).unwrap();
# let json = json_str!(
{
    "type": "geo_shape",
    "tree_levels": 2
}
# );
# assert_eq!(json, mapping);
# }
```
*/
pub trait GeoShapeMapping {
    /**
    Name of the PrefixTree implementation to be used:
    `geohash` for `GeohashPrefixTree` and `quadtree` for `QuadPrefixTree`.
    */
    fn tree() -> Option<Tree> {
        None
    }

    /**
    This parameter may be used instead of `tree_levels` to set an appropriate value
    for the `tree_levels` parameter.
    The value specifies the desired precision and Elasticsearch will calculate the best
    `tree_levels` value to honor this precision.
    The value should be a number followed by an optional distance unit.
    */
    fn precision() -> Option<Distance> {
        None
    }

    /**
    Maximum number of layers to be used by the `PrefixTree`.
    This can be used to control the precision of shape representations and therefore
    how many terms are indexed.
    Defaults to the default value of the chosen `PrefixTree` implementation.
    Since this parameter requires a certain level of understanding of the underlying implementation,
    users may use the `precision` parameter instead.
    However, Elasticsearch only uses the `tree_levels` parameter internally and this is
    what is returned via the mapping API even if you use the `precision` parameter.
    */
    fn tree_levels() -> Option<i32> {
        None
    }

    /**
    The `strategy` parameter defines the approach for how to represent shapes at indexing and search time.
    It also influences the capabilities available so it is recommended to let Elasticsearch
    set this parameter automatically.
    There are two strategies available: `recursive` and `term`.
    Term strategy supports point types only (the `points_only` parameter will be automatically set to `true`)
    while `Recursive` strategy supports all shape types.
    */
    fn strategy() -> Option<Strategy> {
        None
    }

    /**
    Used as a hint to the `PrefixTree` about how precise it should be.
    Defaults to `0.025` (2.5%) with `0.5` as the maximum supported value.

    > PERFORMANCE NOTE: This value will default to `0` if a `precision` or `tree_level` definition is explicitly defined.
    This guarantees spatial precision at the level defined in the mapping.
    This can lead to significant memory usage for high resolution shapes with low error
    (e.g., large shapes at `1m` with < `0.001` error).
    To improve indexing performance (at the cost of query accuracy) explicitly define `tree_level`
    or `precision` along with a reasonable `distance_error_pct`,
    noting that large shapes will have greater false positives.
    */
    fn distance_error_pct() -> Option<f32> {
        None
    }

    /**
    Setting this parameter in the `geo_shape` mapping explicitly sets vertex order for
    the coordinate list of a `geo_shape` field but can be overridden in each individual
    GeoJSON document.
    */
    fn orientation() -> Option<Orientation> {
        None
    }

    /**
    Setting this option to `true` (defaults to `false`) configures the `geo_shape` field
    type for point shapes only (NOTE: Multi-Points are not yet supported).
    This optimizes index and search performance for the geohash and quadtree when it is
    known that only points will be indexed.
    At present `geo_shape` queries can not be executed on geo_point field types.
    This option bridges the gap by improving point performance on a `geo_shape` field
    so that geo_shape queries are optimal on a point only field.
    */
    fn points_only() -> Option<bool> {
        None
    }
}

/** Default mapping for `geo_shape`. */
#[derive(PartialEq, Debug, Default, Clone, Copy)]
pub struct DefaultGeoShapeMapping;
impl GeoShapeMapping for DefaultGeoShapeMapping {}

/** Name of the `PrefixTree` implementation to be used. */
pub enum Tree {
    /** For `GeohashPrefixTree`. */
    Geohash,
    /** For `QuadPrefixTree`. */
    QuadPrefix,
}

impl Serialize for Tree {
    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
    where
        S: Serializer,
    {
        serializer.serialize_str(match *self {
            Tree::Geohash => "geohash",
            Tree::QuadPrefix => "quadtree",
        })
    }
}

/** The strategy defines the approach for how to represent shapes at indexing and search time. */
pub enum Strategy {
    /** Recursive strategy supports all shape types. */
    Recursive,
    /** Term strategy supports point types only. */
    Term,
}

impl Serialize for Strategy {
    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
    where
        S: Serializer,
    {
        serializer.serialize_str(match *self {
            Strategy::Recursive => "recursive",
            Strategy::Term => "term",
        })
    }
}

/**
This parameter defines one of two coordinate system rules (Right-hand or Left-hand)
each of which can be specified in a few different ways.
- Right-hand rule: right, ccw, counterclockwise,
- Left-hand rule: left, cw, clockwise.
The default orientation (counterclockwise) complies with the OGC standard which defines outer
ring vertices in counterclockwise order with inner ring(s) vertices (holes) in clockwise order.
*/
pub enum Orientation {
    /** For `cw`. */
    Clockwise,
    /** For `ccw`. */
    CounterClockwise,
}

impl Serialize for Orientation {
    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
    where
        S: Serializer,
    {
        serializer.serialize_str(match *self {
            Orientation::Clockwise => "cw",
            Orientation::CounterClockwise => "ccw",
        })
    }
}

mod private {
    use super::{
        GeoShapeFieldType,
        GeoShapeMapping,
    };
    use private::field::{
        FieldMapping,
        FieldType,
        SerializeFieldMapping,
        StaticSerialize,
    };
    use serde::ser::SerializeStruct;
    use serde::{
        Serialize,
        Serializer,
    };

    impl<TField, TMapping> FieldType<TMapping, GeoShapePivot> for TField
    where
        TField: GeoShapeFieldType<TMapping> + Serialize,
        TMapping: GeoShapeMapping,
    {
    }

    #[derive(Default)]
    pub struct GeoShapePivot;

    impl<TMapping> FieldMapping<GeoShapePivot> for TMapping
    where
        TMapping: GeoShapeMapping,
    {
        type SerializeFieldMapping = SerializeFieldMapping<TMapping, GeoShapePivot>;

        fn data_type() -> &'static str {
            "geo_shape"
        }
    }

    impl<TMapping> StaticSerialize for SerializeFieldMapping<TMapping, GeoShapePivot>
    where
        TMapping: FieldMapping<GeoShapePivot> + GeoShapeMapping,
    {
        fn static_serialize<S>(serializer: S) -> Result<S::Ok, S::Error>
        where
            S: Serializer,
        {
            let mut state = try!(serializer.serialize_struct("mapping", 8));

            try!(state.serialize_field("type", TMapping::data_type()));

            ser_field!(state, "tree", TMapping::tree());
            ser_field!(state, "precision", TMapping::precision());
            ser_field!(state, "tree_levels", TMapping::tree_levels());
            ser_field!(state, "strategy", TMapping::strategy());
            ser_field!(state, "distance_error_pct", TMapping::distance_error_pct());
            ser_field!(state, "orientation", TMapping::orientation());
            ser_field!(state, "points_only", TMapping::points_only());

            state.end()
        }
    }
}

#[cfg(test)]
mod tests {
    use serde_json;

    use prelude::*;
    use private::field;

    #[derive(Default, Clone)]
    pub struct MyGeoShapeMapping;
    impl GeoShapeMapping for MyGeoShapeMapping {
        fn tree() -> Option<Tree> {
            Some(Tree::Geohash)
        }

        fn precision() -> Option<Distance> {
            Some(Distance(50.0, DistanceUnit::Meters))
        }

        fn tree_levels() -> Option<i32> {
            Some(8)
        }

        fn strategy() -> Option<Strategy> {
            Some(Strategy::Recursive)
        }

        fn distance_error_pct() -> Option<f32> {
            Some(0.5)
        }

        fn orientation() -> Option<Orientation> {
            Some(Orientation::Clockwise)
        }

        fn points_only() -> Option<bool> {
            Some(false)
        }
    }

    #[test]
    fn serialise_mapping_default() {
        let ser = serde_json::to_string(&field::serialize(DefaultGeoShapeMapping)).unwrap();

        let expected = json_str!({
            "type": "geo_shape"
        });

        assert_eq!(expected, ser);
    }

    #[test]
    fn serialise_mapping_custom() {
        let ser = serde_json::to_string(&field::serialize(MyGeoShapeMapping)).unwrap();

        let expected = json_str!({
            "type": "geo_shape",
            "tree": "geohash",
            "precision": "50m",
            "tree_levels": 8,
            "strategy": "recursive",
            "distance_error_pct": 0.5,
            "orientation": "cw",
            "points_only": false
        });

        assert_eq!(expected, ser);
    }

}