Adds support for altitude and 3D GeoJSON

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.
This commit is contained in:
Hans Kristian Flaatten 2013-07-03 10:13:00 +02:00
parent 642fc815e1
commit 46885de00d
4 changed files with 118 additions and 14 deletions

View File

@ -15,6 +15,20 @@ describe('LatLng', function() {
var a = new L.LatLng(NaN, NaN); var a = new L.LatLng(NaN, NaN);
}).to.throwError(); }).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() { describe('#equals', function() {

View File

@ -5,7 +5,7 @@ describe("L.GeoJSON", function () {
properties: {}, properties: {},
geometry: { geometry: {
type: 'Point', type: 'Point',
coordinates: [20, 10] coordinates: [20, 10, 5]
} }
}; };
@ -24,47 +24,79 @@ describe("L.GeoJSON", function () {
}); });
describe("L.Marker#toGeoJSON", 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]); var marker = new L.Marker([10, 20]);
expect(marker.toGeoJSON().geometry).to.eql({ expect(marker.toGeoJSON().geometry).to.eql({
type: 'Point', type: 'Point',
coordinates: [20, 10] 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 () { 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); var circle = new L.Circle([10, 20], 100);
expect(circle.toGeoJSON().geometry).to.eql({ expect(circle.toGeoJSON().geometry).to.eql({
type: 'Point', type: 'Point',
coordinates: [20, 10] 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 () { 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]); var marker = new L.CircleMarker([10, 20]);
expect(marker.toGeoJSON().geometry).to.eql({ expect(marker.toGeoJSON().geometry).to.eql({
type: 'Point', type: 'Point',
coordinates: [20, 10] 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 () { 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]]); var polyline = new L.Polyline([[10, 20], [2, 5]]);
expect(polyline.toGeoJSON().geometry).to.eql({ expect(polyline.toGeoJSON().geometry).to.eql({
type: 'LineString', type: 'LineString',
coordinates: [[20, 10], [5, 2]] 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 () { 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]]]); var multiPolyline = new L.MultiPolyline([[[10, 20], [2, 5]], [[1, 2], [3, 4]]]);
expect(multiPolyline.toGeoJSON().geometry).to.eql({ expect(multiPolyline.toGeoJSON().geometry).to.eql({
type: 'MultiLineString', 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 () { 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]]); var polygon = new L.Polygon([[1, 2], [3, 4], [5, 6]]);
expect(polygon.toGeoJSON().geometry).to.eql({ expect(polygon.toGeoJSON().geometry).to.eql({
type: 'Polygon', 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]]]); var polygon = new L.Polygon([[[1, 2], [3, 4], [5, 6]], [[7, 8], [9, 10], [11, 12]]]);
expect(polygon.toGeoJSON().geometry).to.eql({ expect(polygon.toGeoJSON().geometry).to.eql({
type: 'Polygon', 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 () { 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]]]); var multiPolygon = new L.MultiPolygon([[[1, 2], [3, 4], [5, 6]]]);
expect(multiPolygon.toGeoJSON().geometry).to.eql({ expect(multiPolygon.toGeoJSON().geometry).to.eql({
type: 'MultiPolygon', 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 () { 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]), var marker = new L.Marker([10, 20]),
polyline = new L.Polyline([[10, 20], [2, 5]]), polyline = new L.Polyline([[10, 20], [2, 5]]),
layerGroup = new L.LayerGroup([marker, polyline]); 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 () { it("ensures that every member is a Feature", function () {
var tileLayer = new L.TileLayer(), var tileLayer = new L.TileLayer(),
layerGroup = new L.LayerGroup([tileLayer]); layerGroup = new L.LayerGroup([tileLayer]);

View File

@ -2,7 +2,7 @@
* L.LatLng represents a geographical point with latitude and longitude coordinates. * 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), var lat = parseFloat(rawLat),
lng = parseFloat(rawLng); lng = parseFloat(rawLng);
@ -12,6 +12,10 @@ L.LatLng = function (rawLat, rawLng) { // (Number, Number)
this.lat = lat; this.lat = lat;
this.lng = lng; this.lng = lng;
if (typeof rawAltitude !== 'undefined') {
this.altitude = parseFloat(rawAltitude);
}
}; };
L.extend(L.LatLng, { L.extend(L.LatLng, {
@ -75,7 +79,7 @@ L.latLng = function (a, b) { // (LatLng) or ([Number, Number]) or (Number, Numbe
return a; return a;
} }
if (L.Util.isArray(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) { if (a === undefined || a === null) {
return a; return a;

View File

@ -128,7 +128,7 @@ L.extend(L.GeoJSON, {
}, },
coordsToLatLng: function (coords) { // (Array[, Boolean]) -> LatLng 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 coordsToLatLngs: function (coords, levelsDeep, coordsToLatLng) { // (Array[, Number, Function]) -> Array
@ -147,7 +147,11 @@ L.extend(L.GeoJSON, {
}, },
latLngToCoords: function (latLng) { latLngToCoords: function (latLng) {
if (typeof latLng.altitude === 'undefined') {
return [latLng.lng, latLng.lat]; return [latLng.lng, latLng.lat];
} else {
return [latLng.lng, latLng.lat, latLng.altitude];
}
}, },
latLngsToCoords: function (latLngs) { latLngsToCoords: function (latLngs) {