From 46885de00d76207d8974d092d1caff1e2ef03d35 Mon Sep 17 00:00:00 2001 From: Hans Kristian Flaatten Date: Wed, 3 Jul 2013 10:13:00 +0200 Subject: [PATCH 1/2] Adds support for altitude and 3D GeoJSON MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This commit adds a third parameter to the L.LatLon class for specifying altitude. This is in turn stored in the `.latitude` property for the LatLng instance. Latitude property will only be set if the latitude parameter is not undefined, this is done in order to ensure backwards compability. ```javascript var latlng = new L.LatLng(10, 20, 30); console.log(latlng.altitude); // prints '30' to the console ``` Similar functionality has been added to L.GeoJSON coordsToLatLng() and latLngToCoords() methods in order to handle import and export of 3D GeoJSON. ```javascript var geoJSON = { type: 'Feature' ,properties: {} ,geometry: { type: 'Point' ,coordinates: [20, 10, 30] } } var layer = new L.GeoJSON(); layer.addData(geoJSON); console.log(layer.getLayers()[0].getLatLng().altitude); ``` `NB` It is important to notice that no logic has been added in order to prevent latitude and longitude to change without appropirate change in altitude – this must be handled by the application. --- spec/suites/geo/LatLngSpec.js | 14 +++++ spec/suites/layer/GeoJSONSpec.js | 102 ++++++++++++++++++++++++++++--- src/geo/LatLng.js | 8 ++- src/layer/GeoJSON.js | 8 ++- 4 files changed, 118 insertions(+), 14 deletions(-) diff --git a/spec/suites/geo/LatLngSpec.js b/spec/suites/geo/LatLngSpec.js index aefea668edb..933fe324715 100644 --- a/spec/suites/geo/LatLngSpec.js +++ b/spec/suites/geo/LatLngSpec.js @@ -15,6 +15,20 @@ describe('LatLng', function() { var a = new L.LatLng(NaN, NaN); }).to.throwError(); }); + + it ('does not set altitude if undefined', function () { + var a = new L.LatLng(25, 74); + expect(typeof a.altitude).to.eql('undefined'); + }); + + it ('sets altitude', function () { + var a = new L.LatLng(25, 74, 50); + expect(a.altitude).to.eql(50); + + var b = new L.LatLng(-25, -74, -50); + expect(b.altitude).to.eql(-50); + }); + }); describe('#equals', function() { diff --git a/spec/suites/layer/GeoJSONSpec.js b/spec/suites/layer/GeoJSONSpec.js index 3ffc990bf55..c7f71ba49e2 100644 --- a/spec/suites/layer/GeoJSONSpec.js +++ b/spec/suites/layer/GeoJSONSpec.js @@ -5,7 +5,7 @@ describe("L.GeoJSON", function () { properties: {}, geometry: { type: 'Point', - coordinates: [20, 10] + coordinates: [20, 10, 5] } }; @@ -24,47 +24,79 @@ describe("L.GeoJSON", function () { }); describe("L.Marker#toGeoJSON", function () { - it("returns a Point object", function () { + it("returns a 2D Point object", function () { var marker = new L.Marker([10, 20]); expect(marker.toGeoJSON().geometry).to.eql({ type: 'Point', coordinates: [20, 10] }); }); + + it("returns a 3D Point object", function () { + var marker = new L.Marker([10, 20, 30]); + expect(marker.toGeoJSON().geometry).to.eql({ + type: 'Point', + coordinates: [20, 10, 30] + }); + }); }); describe("L.Circle#toGeoJSON", function () { - it("returns a Point object", function () { + it("returns a 2D Point object", function () { var circle = new L.Circle([10, 20], 100); expect(circle.toGeoJSON().geometry).to.eql({ type: 'Point', coordinates: [20, 10] }); }); + + it("returns a 3D Point object", function () { + var circle = new L.Circle([10, 20, 30], 100); + expect(circle.toGeoJSON().geometry).to.eql({ + type: 'Point', + coordinates: [20, 10, 30] + }); + }); }); describe("L.CircleMarker#toGeoJSON", function () { - it("returns a Point object", function () { + it("returns a 2D Point object", function () { var marker = new L.CircleMarker([10, 20]); expect(marker.toGeoJSON().geometry).to.eql({ type: 'Point', coordinates: [20, 10] }); }); + + it("returns a 3D Point object", function () { + var marker = new L.CircleMarker([10, 20, 30]); + expect(marker.toGeoJSON().geometry).to.eql({ + type: 'Point', + coordinates: [20, 10, 30] + }); + }); }); describe("L.Polyline#toGeoJSON", function () { - it("returns a LineString object", function () { + it("returns a 2D LineString object", function () { var polyline = new L.Polyline([[10, 20], [2, 5]]); expect(polyline.toGeoJSON().geometry).to.eql({ type: 'LineString', coordinates: [[20, 10], [5, 2]] }); }); + + it("returns a 3D LineString object", function () { + var polyline = new L.Polyline([[10, 20, 30], [2, 5, 10]]); + expect(polyline.toGeoJSON().geometry).to.eql({ + type: 'LineString', + coordinates: [[20, 10, 30], [5, 2, 10]] + }); + }); }); describe("L.MultiPolyline#toGeoJSON", function () { - it("returns a MultiLineString object", function () { + it("returns a 2D MultiLineString object", function () { var multiPolyline = new L.MultiPolyline([[[10, 20], [2, 5]], [[1, 2], [3, 4]]]); expect(multiPolyline.toGeoJSON().geometry).to.eql({ type: 'MultiLineString', @@ -74,10 +106,21 @@ describe("L.MultiPolyline#toGeoJSON", function () { ] }); }); + + it("returns a 3D MultiLineString object", function () { + var multiPolyline = new L.MultiPolyline([[[10, 20, 30], [2, 5, 10]], [[1, 2, 3], [4, 5, 6]]]); + expect(multiPolyline.toGeoJSON().geometry).to.eql({ + type: 'MultiLineString', + coordinates: [ + [[20, 10, 30], [5, 2, 10]], + [[2, 1, 3], [5, 4, 6]] + ] + }); + }); }); describe("L.Polygon#toGeoJSON", function () { - it("returns a Polygon object (no holes)", function () { + it("returns a 2D Polygon object (no holes)", function () { var polygon = new L.Polygon([[1, 2], [3, 4], [5, 6]]); expect(polygon.toGeoJSON().geometry).to.eql({ type: 'Polygon', @@ -85,7 +128,15 @@ describe("L.Polygon#toGeoJSON", function () { }); }); - it("returns a Polygon object (with holes)", function () { + it("returns a 3D Polygon object (no holes)", function () { + var polygon = new L.Polygon([[1, 2, 3], [4, 5, 6], [7, 8, 9]]); + expect(polygon.toGeoJSON().geometry).to.eql({ + type: 'Polygon', + coordinates: [[[2, 1, 3], [5, 4, 6], [8, 7, 9], [2, 1, 3]]] + }); + }); + + it("returns a 2D Polygon object (with holes)", function () { var polygon = new L.Polygon([[[1, 2], [3, 4], [5, 6]], [[7, 8], [9, 10], [11, 12]]]); expect(polygon.toGeoJSON().geometry).to.eql({ type: 'Polygon', @@ -95,10 +146,21 @@ describe("L.Polygon#toGeoJSON", function () { ] }); }); + + it("returns a 3D Polygon object (with holes)", function () { + var polygon = new L.Polygon([[[1, 2, 3], [4, 5, 6], [7, 8, 9]], [[10, 11, 12], [13, 14, 15], [16, 17, 18]]]); + expect(polygon.toGeoJSON().geometry).to.eql({ + type: 'Polygon', + coordinates: [ + [[2, 1, 3], [5, 4, 6], [8, 7, 9], [2, 1, 3]], + [[11, 10, 12], [14, 13, 15], [17, 16, 18], [11, 10, 12]] + ] + }); + }); }); describe("L.MultiPolygon#toGeoJSON", function () { - it("returns a MultiPolygon object", function () { + it("returns a 2D MultiPolygon object", function () { var multiPolygon = new L.MultiPolygon([[[1, 2], [3, 4], [5, 6]]]); expect(multiPolygon.toGeoJSON().geometry).to.eql({ type: 'MultiPolygon', @@ -107,10 +169,20 @@ describe("L.MultiPolygon#toGeoJSON", function () { ] }); }); + + it("returns a 3D MultiPolygon object", function () { + var multiPolygon = new L.MultiPolygon([[[1, 2, 3], [4, 5, 6], [7, 8, 9]]]); + expect(multiPolygon.toGeoJSON().geometry).to.eql({ + type: 'MultiPolygon', + coordinates: [ + [[[2, 1, 3], [5, 4, 6], [8, 7, 9], [2, 1, 3]]] + ] + }); + }); }); describe("L.LayerGroup#toGeoJSON", function () { - it("returns a FeatureCollection object", function () { + it("returns a 2D FeatureCollection object", function () { var marker = new L.Marker([10, 20]), polyline = new L.Polyline([[10, 20], [2, 5]]), layerGroup = new L.LayerGroup([marker, polyline]); @@ -120,6 +192,16 @@ describe("L.LayerGroup#toGeoJSON", function () { }); }); + it("returns a 3D FeatureCollection object", function () { + var marker = new L.Marker([10, 20, 30]), + polyline = new L.Polyline([[10, 20, 30], [2, 5, 10]]), + layerGroup = new L.LayerGroup([marker, polyline]); + expect(layerGroup.toGeoJSON()).to.eql({ + type: 'FeatureCollection', + features: [marker.toGeoJSON(), polyline.toGeoJSON()] + }); + }); + it("ensures that every member is a Feature", function () { var tileLayer = new L.TileLayer(), layerGroup = new L.LayerGroup([tileLayer]); diff --git a/src/geo/LatLng.js b/src/geo/LatLng.js index 9e2a453cc4d..26eb709dcff 100644 --- a/src/geo/LatLng.js +++ b/src/geo/LatLng.js @@ -2,7 +2,7 @@ * L.LatLng represents a geographical point with latitude and longitude coordinates. */ -L.LatLng = function (rawLat, rawLng) { // (Number, Number) +L.LatLng = function (rawLat, rawLng, rawAltitude) { // (Number, Number, Number) var lat = parseFloat(rawLat), lng = parseFloat(rawLng); @@ -12,6 +12,10 @@ L.LatLng = function (rawLat, rawLng) { // (Number, Number) this.lat = lat; this.lng = lng; + + if (typeof rawAltitude !== 'undefined') { + this.altitude = parseFloat(rawAltitude); + } }; L.extend(L.LatLng, { @@ -75,7 +79,7 @@ L.latLng = function (a, b) { // (LatLng) or ([Number, Number]) or (Number, Numbe return a; } if (L.Util.isArray(a)) { - return new L.LatLng(a[0], a[1]); + return new L.LatLng(a[0], a[1], a[2]); } if (a === undefined || a === null) { return a; diff --git a/src/layer/GeoJSON.js b/src/layer/GeoJSON.js index 38cc02d66e9..04d2ba1c14d 100644 --- a/src/layer/GeoJSON.js +++ b/src/layer/GeoJSON.js @@ -128,7 +128,7 @@ L.extend(L.GeoJSON, { }, coordsToLatLng: function (coords) { // (Array[, Boolean]) -> LatLng - return new L.LatLng(coords[1], coords[0]); + return new L.LatLng(coords[1], coords[0], coords[2]); }, coordsToLatLngs: function (coords, levelsDeep, coordsToLatLng) { // (Array[, Number, Function]) -> Array @@ -147,7 +147,11 @@ L.extend(L.GeoJSON, { }, latLngToCoords: function (latLng) { - return [latLng.lng, latLng.lat]; + if (typeof latLng.altitude === 'undefined') { + return [latLng.lng, latLng.lat]; + } else { + return [latLng.lng, latLng.lat, latLng.altitude]; + } }, latLngsToCoords: function (latLngs) { From 8e98e52b88499fff1cb93265f708487b64354a67 Mon Sep 17 00:00:00 2001 From: Hans Kristian Flaatten Date: Wed, 3 Jul 2013 15:44:57 +0200 Subject: [PATCH 2/2] Renames L.LatLng property .altitude to .alt --- spec/suites/geo/LatLngSpec.js | 6 +++--- src/geo/LatLng.js | 6 +++--- src/layer/GeoJSON.js | 4 ++-- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/spec/suites/geo/LatLngSpec.js b/spec/suites/geo/LatLngSpec.js index 933fe324715..f3b8d745ab9 100644 --- a/spec/suites/geo/LatLngSpec.js +++ b/spec/suites/geo/LatLngSpec.js @@ -18,15 +18,15 @@ describe('LatLng', function() { it ('does not set altitude if undefined', function () { var a = new L.LatLng(25, 74); - expect(typeof a.altitude).to.eql('undefined'); + expect(typeof a.alt).to.eql('undefined'); }); it ('sets altitude', function () { var a = new L.LatLng(25, 74, 50); - expect(a.altitude).to.eql(50); + expect(a.alt).to.eql(50); var b = new L.LatLng(-25, -74, -50); - expect(b.altitude).to.eql(-50); + expect(b.alt).to.eql(-50); }); }); diff --git a/src/geo/LatLng.js b/src/geo/LatLng.js index 26eb709dcff..fbc1f56c0c5 100644 --- a/src/geo/LatLng.js +++ b/src/geo/LatLng.js @@ -2,7 +2,7 @@ * L.LatLng represents a geographical point with latitude and longitude coordinates. */ -L.LatLng = function (rawLat, rawLng, rawAltitude) { // (Number, Number, Number) +L.LatLng = function (rawLat, rawLng, rawAlt) { // (Number, Number, Number) var lat = parseFloat(rawLat), lng = parseFloat(rawLng); @@ -13,8 +13,8 @@ L.LatLng = function (rawLat, rawLng, rawAltitude) { // (Number, Number, Number) this.lat = lat; this.lng = lng; - if (typeof rawAltitude !== 'undefined') { - this.altitude = parseFloat(rawAltitude); + if (typeof rawAlt !== 'undefined') { + this.alt = parseFloat(rawAlt); } }; diff --git a/src/layer/GeoJSON.js b/src/layer/GeoJSON.js index 04d2ba1c14d..32798f1d7ce 100644 --- a/src/layer/GeoJSON.js +++ b/src/layer/GeoJSON.js @@ -147,10 +147,10 @@ L.extend(L.GeoJSON, { }, latLngToCoords: function (latLng) { - if (typeof latLng.altitude === 'undefined') { + if (typeof latLng.alt === 'undefined') { return [latLng.lng, latLng.lat]; } else { - return [latLng.lng, latLng.lat, latLng.altitude]; + return [latLng.lng, latLng.lat, latLng.alt]; } },