UNPKG

13.6 kBJavaScriptView Raw
1import {LayerGroup} from './LayerGroup';
2import {FeatureGroup} from './FeatureGroup';
3import * as Util from '../core/Util';
4import {Marker} from './marker/Marker';
5import {Circle} from './vector/Circle';
6import {CircleMarker} from './vector/CircleMarker';
7import {Polyline} from './vector/Polyline';
8import {Polygon} from './vector/Polygon';
9import {LatLng} from '../geo/LatLng';
10import * as LineUtil from '../geometry/LineUtil';
11
12
13/*
14 * @class GeoJSON
15 * @aka L.GeoJSON
16 * @inherits FeatureGroup
17 *
18 * Represents a GeoJSON object or an array of GeoJSON objects. Allows you to parse
19 * GeoJSON data and display it on the map. Extends `FeatureGroup`.
20 *
21 * @example
22 *
23 * ```js
24 * L.geoJSON(data, {
25 * style: function (feature) {
26 * return {color: feature.properties.color};
27 * }
28 * }).bindPopup(function (layer) {
29 * return layer.feature.properties.description;
30 * }).addTo(map);
31 * ```
32 */
33
34export var GeoJSON = FeatureGroup.extend({
35
36 /* @section
37 * @aka GeoJSON options
38 *
39 * @option pointToLayer: Function = *
40 * A `Function` defining how GeoJSON points spawn Leaflet layers. It is internally
41 * called when data is added, passing the GeoJSON point feature and its `LatLng`.
42 * The default is to spawn a default `Marker`:
43 * ```js
44 * function(geoJsonPoint, latlng) {
45 * return L.marker(latlng);
46 * }
47 * ```
48 *
49 * @option style: Function = *
50 * A `Function` defining the `Path options` for styling GeoJSON lines and polygons,
51 * called internally when data is added.
52 * The default value is to not override any defaults:
53 * ```js
54 * function (geoJsonFeature) {
55 * return {}
56 * }
57 * ```
58 *
59 * @option onEachFeature: Function = *
60 * A `Function` that will be called once for each created `Feature`, after it has
61 * been created and styled. Useful for attaching events and popups to features.
62 * The default is to do nothing with the newly created layers:
63 * ```js
64 * function (feature, layer) {}
65 * ```
66 *
67 * @option filter: Function = *
68 * A `Function` that will be used to decide whether to include a feature or not.
69 * The default is to include all features:
70 * ```js
71 * function (geoJsonFeature) {
72 * return true;
73 * }
74 * ```
75 * Note: dynamically changing the `filter` option will have effect only on newly
76 * added data. It will _not_ re-evaluate already included features.
77 *
78 * @option coordsToLatLng: Function = *
79 * A `Function` that will be used for converting GeoJSON coordinates to `LatLng`s.
80 * The default is the `coordsToLatLng` static method.
81 */
82
83 initialize: function (geojson, options) {
84 Util.setOptions(this, options);
85
86 this._layers = {};
87
88 if (geojson) {
89 this.addData(geojson);
90 }
91 },
92
93 // @method addData( <GeoJSON> data ): this
94 // Adds a GeoJSON object to the layer.
95 addData: function (geojson) {
96 var features = Util.isArray(geojson) ? geojson : geojson.features,
97 i, len, feature;
98
99 if (features) {
100 for (i = 0, len = features.length; i < len; i++) {
101 // only add this if geometry or geometries are set and not null
102 feature = features[i];
103 if (feature.geometries || feature.geometry || feature.features || feature.coordinates) {
104 this.addData(feature);
105 }
106 }
107 return this;
108 }
109
110 var options = this.options;
111
112 if (options.filter && !options.filter(geojson)) { return this; }
113
114 var layer = geometryToLayer(geojson, options);
115 if (!layer) {
116 return this;
117 }
118 layer.feature = asFeature(geojson);
119
120 layer.defaultOptions = layer.options;
121 this.resetStyle(layer);
122
123 if (options.onEachFeature) {
124 options.onEachFeature(geojson, layer);
125 }
126
127 return this.addLayer(layer);
128 },
129
130 // @method resetStyle( <Path> layer ): this
131 // Resets the given vector layer's style to the original GeoJSON style, useful for resetting style after hover events.
132 resetStyle: function (layer) {
133 // reset any custom styles
134 layer.options = Util.extend({}, layer.defaultOptions);
135 this._setLayerStyle(layer, this.options.style);
136 return this;
137 },
138
139 // @method setStyle( <Function> style ): this
140 // Changes styles of GeoJSON vector layers with the given style function.
141 setStyle: function (style) {
142 return this.eachLayer(function (layer) {
143 this._setLayerStyle(layer, style);
144 }, this);
145 },
146
147 _setLayerStyle: function (layer, style) {
148 if (layer.setStyle) {
149 if (typeof style === 'function') {
150 style = style(layer.feature);
151 }
152 layer.setStyle(style);
153 }
154 }
155});
156
157// @section
158// There are several static functions which can be called without instantiating L.GeoJSON:
159
160// @function geometryToLayer(featureData: Object, options?: GeoJSON options): Layer
161// Creates a `Layer` from a given GeoJSON feature. Can use a custom
162// [`pointToLayer`](#geojson-pointtolayer) and/or [`coordsToLatLng`](#geojson-coordstolatlng)
163// functions if provided as options.
164export function geometryToLayer(geojson, options) {
165
166 var geometry = geojson.type === 'Feature' ? geojson.geometry : geojson,
167 coords = geometry ? geometry.coordinates : null,
168 layers = [],
169 pointToLayer = options && options.pointToLayer,
170 _coordsToLatLng = options && options.coordsToLatLng || coordsToLatLng,
171 latlng, latlngs, i, len;
172
173 if (!coords && !geometry) {
174 return null;
175 }
176
177 switch (geometry.type) {
178 case 'Point':
179 latlng = _coordsToLatLng(coords);
180 return pointToLayer ? pointToLayer(geojson, latlng) : new Marker(latlng);
181
182 case 'MultiPoint':
183 for (i = 0, len = coords.length; i < len; i++) {
184 latlng = _coordsToLatLng(coords[i]);
185 layers.push(pointToLayer ? pointToLayer(geojson, latlng) : new Marker(latlng));
186 }
187 return new FeatureGroup(layers);
188
189 case 'LineString':
190 case 'MultiLineString':
191 latlngs = coordsToLatLngs(coords, geometry.type === 'LineString' ? 0 : 1, _coordsToLatLng);
192 return new Polyline(latlngs, options);
193
194 case 'Polygon':
195 case 'MultiPolygon':
196 latlngs = coordsToLatLngs(coords, geometry.type === 'Polygon' ? 1 : 2, _coordsToLatLng);
197 return new Polygon(latlngs, options);
198
199 case 'GeometryCollection':
200 for (i = 0, len = geometry.geometries.length; i < len; i++) {
201 var layer = geometryToLayer({
202 geometry: geometry.geometries[i],
203 type: 'Feature',
204 properties: geojson.properties
205 }, options);
206
207 if (layer) {
208 layers.push(layer);
209 }
210 }
211 return new FeatureGroup(layers);
212
213 default:
214 throw new Error('Invalid GeoJSON object.');
215 }
216}
217
218// @function coordsToLatLng(coords: Array): LatLng
219// Creates a `LatLng` object from an array of 2 numbers (longitude, latitude)
220// or 3 numbers (longitude, latitude, altitude) used in GeoJSON for points.
221export function coordsToLatLng(coords) {
222 return new LatLng(coords[1], coords[0], coords[2]);
223}
224
225// @function coordsToLatLngs(coords: Array, levelsDeep?: Number, coordsToLatLng?: Function): Array
226// Creates a multidimensional array of `LatLng`s from a GeoJSON coordinates array.
227// `levelsDeep` specifies the nesting level (0 is for an array of points, 1 for an array of arrays of points, etc., 0 by default).
228// Can use a custom [`coordsToLatLng`](#geojson-coordstolatlng) function.
229export function coordsToLatLngs(coords, levelsDeep, _coordsToLatLng) {
230 var latlngs = [];
231
232 for (var i = 0, len = coords.length, latlng; i < len; i++) {
233 latlng = levelsDeep ?
234 coordsToLatLngs(coords[i], levelsDeep - 1, _coordsToLatLng) :
235 (_coordsToLatLng || coordsToLatLng)(coords[i]);
236
237 latlngs.push(latlng);
238 }
239
240 return latlngs;
241}
242
243// @function latLngToCoords(latlng: LatLng, precision?: Number): Array
244// Reverse of [`coordsToLatLng`](#geojson-coordstolatlng)
245export function latLngToCoords(latlng, precision) {
246 precision = typeof precision === 'number' ? precision : 6;
247 return latlng.alt !== undefined ?
248 [Util.formatNum(latlng.lng, precision), Util.formatNum(latlng.lat, precision), Util.formatNum(latlng.alt, precision)] :
249 [Util.formatNum(latlng.lng, precision), Util.formatNum(latlng.lat, precision)];
250}
251
252// @function latLngsToCoords(latlngs: Array, levelsDeep?: Number, closed?: Boolean): Array
253// Reverse of [`coordsToLatLngs`](#geojson-coordstolatlngs)
254// `closed` determines whether the first point should be appended to the end of the array to close the feature, only used when `levelsDeep` is 0. False by default.
255export function latLngsToCoords(latlngs, levelsDeep, closed, precision) {
256 var coords = [];
257
258 for (var i = 0, len = latlngs.length; i < len; i++) {
259 coords.push(levelsDeep ?
260 latLngsToCoords(latlngs[i], levelsDeep - 1, closed, precision) :
261 latLngToCoords(latlngs[i], precision));
262 }
263
264 if (!levelsDeep && closed) {
265 coords.push(coords[0]);
266 }
267
268 return coords;
269}
270
271export function getFeature(layer, newGeometry) {
272 return layer.feature ?
273 Util.extend({}, layer.feature, {geometry: newGeometry}) :
274 asFeature(newGeometry);
275}
276
277// @function asFeature(geojson: Object): Object
278// Normalize GeoJSON geometries/features into GeoJSON features.
279export function asFeature(geojson) {
280 if (geojson.type === 'Feature' || geojson.type === 'FeatureCollection') {
281 return geojson;
282 }
283
284 return {
285 type: 'Feature',
286 properties: {},
287 geometry: geojson
288 };
289}
290
291var PointToGeoJSON = {
292 toGeoJSON: function (precision) {
293 return getFeature(this, {
294 type: 'Point',
295 coordinates: latLngToCoords(this.getLatLng(), precision)
296 });
297 }
298};
299
300// @namespace Marker
301// @method toGeoJSON(precision?: Number): Object
302// `precision` is the number of decimal places for coordinates.
303// The default value is 6 places.
304// Returns a [`GeoJSON`](http://en.wikipedia.org/wiki/GeoJSON) representation of the marker (as a GeoJSON `Point` Feature).
305Marker.include(PointToGeoJSON);
306
307// @namespace CircleMarker
308// @method toGeoJSON(precision?: Number): Object
309// `precision` is the number of decimal places for coordinates.
310// The default value is 6 places.
311// Returns a [`GeoJSON`](http://en.wikipedia.org/wiki/GeoJSON) representation of the circle marker (as a GeoJSON `Point` Feature).
312Circle.include(PointToGeoJSON);
313CircleMarker.include(PointToGeoJSON);
314
315
316// @namespace Polyline
317// @method toGeoJSON(precision?: Number): Object
318// `precision` is the number of decimal places for coordinates.
319// The default value is 6 places.
320// Returns a [`GeoJSON`](http://en.wikipedia.org/wiki/GeoJSON) representation of the polyline (as a GeoJSON `LineString` or `MultiLineString` Feature).
321Polyline.include({
322 toGeoJSON: function (precision) {
323 var multi = !LineUtil.isFlat(this._latlngs);
324
325 var coords = latLngsToCoords(this._latlngs, multi ? 1 : 0, false, precision);
326
327 return getFeature(this, {
328 type: (multi ? 'Multi' : '') + 'LineString',
329 coordinates: coords
330 });
331 }
332});
333
334// @namespace Polygon
335// @method toGeoJSON(precision?: Number): Object
336// `precision` is the number of decimal places for coordinates.
337// The default value is 6 places.
338// Returns a [`GeoJSON`](http://en.wikipedia.org/wiki/GeoJSON) representation of the polygon (as a GeoJSON `Polygon` or `MultiPolygon` Feature).
339Polygon.include({
340 toGeoJSON: function (precision) {
341 var holes = !LineUtil.isFlat(this._latlngs),
342 multi = holes && !LineUtil.isFlat(this._latlngs[0]);
343
344 var coords = latLngsToCoords(this._latlngs, multi ? 2 : holes ? 1 : 0, true, precision);
345
346 if (!holes) {
347 coords = [coords];
348 }
349
350 return getFeature(this, {
351 type: (multi ? 'Multi' : '') + 'Polygon',
352 coordinates: coords
353 });
354 }
355});
356
357
358// @namespace LayerGroup
359LayerGroup.include({
360 toMultiPoint: function (precision) {
361 var coords = [];
362
363 this.eachLayer(function (layer) {
364 coords.push(layer.toGeoJSON(precision).geometry.coordinates);
365 });
366
367 return getFeature(this, {
368 type: 'MultiPoint',
369 coordinates: coords
370 });
371 },
372
373 // @method toGeoJSON(precision?: Number): Object
374 // `precision` is the number of decimal places for coordinates.
375 // The default value is 6 places.
376 // Returns a [`GeoJSON`](http://en.wikipedia.org/wiki/GeoJSON) representation of the layer group (as a GeoJSON `FeatureCollection`, `GeometryCollection`, or `MultiPoint`).
377 toGeoJSON: function (precision) {
378
379 var type = this.feature && this.feature.geometry && this.feature.geometry.type;
380
381 if (type === 'MultiPoint') {
382 return this.toMultiPoint(precision);
383 }
384
385 var isGeometryCollection = type === 'GeometryCollection',
386 jsons = [];
387
388 this.eachLayer(function (layer) {
389 if (layer.toGeoJSON) {
390 var json = layer.toGeoJSON(precision);
391 if (isGeometryCollection) {
392 jsons.push(json.geometry);
393 } else {
394 var feature = asFeature(json);
395 // Squash nested feature collections
396 if (feature.type === 'FeatureCollection') {
397 jsons.push.apply(jsons, feature.features);
398 } else {
399 jsons.push(feature);
400 }
401 }
402 }
403 });
404
405 if (isGeometryCollection) {
406 return getFeature(this, {
407 geometries: jsons,
408 type: 'GeometryCollection'
409 });
410 }
411
412 return {
413 type: 'FeatureCollection',
414 features: jsons
415 };
416 }
417});
418
419// @namespace GeoJSON
420// @factory L.geoJSON(geojson?: Object, options?: GeoJSON options)
421// Creates a GeoJSON layer. Optionally accepts an object in
422// [GeoJSON format](https://tools.ietf.org/html/rfc7946) to display on the map
423// (you can alternatively add it later with `addData` method) and an `options` object.
424export function geoJSON(geojson, options) {
425 return new GeoJSON(geojson, options);
426}
427
428// Backward compatibility.
429export var geoJson = geoJSON;