From 0da1f04b41eb6437551ecd813a3c13c48a81b842 Mon Sep 17 00:00:00 2001 From: Arnoud de Vries Date: Tue, 13 Jun 2017 14:03:51 +0200 Subject: [PATCH] Support PointZ, PolyLineZ and PolygonZ --- src/extent.js | 8 +++++++- src/geojson.js | 38 +++++++++++++++++++++++++++++++------- src/points.js | 33 ++++++++++++++++++++++----------- src/poly.js | 47 +++++++++++++++++++++++++++++++++++------------ src/write.js | 13 ++++++++++--- src/zip.js | 2 +- 6 files changed, 106 insertions(+), 35 deletions(-) diff --git a/src/extent.js b/src/extent.js index 64f929f3..c8f04e20 100644 --- a/src/extent.js +++ b/src/extent.js @@ -3,6 +3,8 @@ module.exports.enlarge = function enlargeExtent(extent, pt) { if (pt[0] > extent.xmax) extent.xmax = pt[0]; if (pt[1] < extent.ymin) extent.ymin = pt[1]; if (pt[1] > extent.ymax) extent.ymax = pt[1]; + if (pt[2] && pt[2] < extent.zmin) extent.zmin = pt[2]; + if (pt[2] && pt[2] > extent.zmax) extent.zmax = pt[2]; return extent; }; @@ -11,6 +13,8 @@ module.exports.enlargeExtent = function enlargeExtent(extent, ext) { if (ext.xmin < extent.xmin) extent.xmin = ext.xmin; if (ext.ymax > extent.ymax) extent.ymax = ext.ymax; if (ext.ymin < extent.ymin) extent.ymin = ext.ymin; + if (ext.zmax && ext.zmax > extent.zmax) extent.zmax = ext.zmax; + if (ext.zmin && ext.zmin < extent.zmin) extent.zmin = ext.zmin; return extent; }; @@ -18,7 +22,9 @@ module.exports.blank = function() { return { xmin: Number.MAX_VALUE, ymin: Number.MAX_VALUE, + zmin: Number.MAX_VALUE, xmax: -Number.MAX_VALUE, - ymax: -Number.MAX_VALUE + ymax: -Number.MAX_VALUE, + zmax: -Number.MAX_VALUE }; }; diff --git a/src/geojson.js b/src/geojson.js index dfad6eee..3350dbea 100644 --- a/src/geojson.js +++ b/src/geojson.js @@ -1,13 +1,21 @@ -module.exports.point = justType('Point', 'POINT'); -module.exports.line = justType('LineString', 'POLYLINE'); -module.exports.polygon = justType('Polygon', 'POLYGON'); +module.exports.point = justType('Point', 'POINT', false); +module.exports.pointZ = justType('Point', 'POINTZ', true); +module.exports.line = justType('LineString', 'POLYLINE', false); +module.exports.lineZ = justType('LineString', 'POLYLINEZ', true); +module.exports.polygon = justType('Polygon', 'POLYGON', false); +module.exports.polygonZ = justType('Polygon', 'POLYGONZ', true); -function justType(type, TYPE) { +function justType(type, TYPE, just3d) { return function(gj) { - var oftype = gj.features.filter(isType(type)); + var ofType = gj.features.filter(isType(type)); + var ofDimension = ofType.filter(isOfDimension(TYPE, just3d)); + return { - geometries: (TYPE === 'POLYGON' || TYPE === 'POLYLINE') ? [oftype.map(justCoords)] : oftype.map(justCoords), - properties: oftype.map(justProps), + geometries: (TYPE === 'POLYLINE' || TYPE === 'POLYLINEZ' || + TYPE === 'POLYGON' || TYPE === 'POLYGONZ') ? + [ofDimension.map(justCoords)] : + ofDimension.map(justCoords), + properties: ofDimension.map(justProps), type: TYPE }; }; @@ -17,6 +25,7 @@ function justCoords(t) { if (t.geometry.coordinates[0] !== undefined && t.geometry.coordinates[0][0] !== undefined && t.geometry.coordinates[0][0][0] !== undefined) { + // Unwraps rings return t.geometry.coordinates[0]; } else { return t.geometry.coordinates; @@ -30,3 +39,18 @@ function justProps(t) { function isType(t) { return function(f) { return f.geometry.type === t; }; } + +function isOfDimension(TYPE, just3d) { + return function(f) { + var coordinates; + if (TYPE === 'POINT' || TYPE === 'POINTZ') { + coordinates = f.geometry.coordinates; + } + else { + coordinates = Array.isArray(f.geometry.coordinates[0][0]) ? + f.geometry.coordinates[0][0] : + f.geometry.coordinates[0]; + } + return just3d ? coordinates.length === 3 : coordinates.length === 2; + } +} diff --git a/src/points.js b/src/points.js index af27df9f..8885c1b9 100644 --- a/src/points.js +++ b/src/points.js @@ -1,28 +1,35 @@ var ext = require('./extent'); +var types = require('./types'); module.exports.write = function writePoints(coordinates, extent, shpView, shxView) { - - var contentLength = 28, // 8 header, 20 content - fileLength = 100, + var + fileLength = 100, // starts with 100 as this is the fixed header length shpI = 0, shxI = 0; - coordinates.forEach(function writePoint(coords, i) { + coordinates.forEach(function writePoint(coords, index) { + var is3d = !!coords[2], + contentLength = is3d ? 44 : 28; // 8 header + 20 content, 8 header + 36 content when 3d // HEADER // 4 record number // 4 content length in 16-bit words (20/2) - shpView.setInt32(shpI, i); - shpView.setInt32(shpI + 4, 10); + shpView.setInt32(shpI, index); + shpView.setInt32(shpI + 4, (contentLength - 8) / 2); // record // (8 + 8) + 4 = 20 content length - shpView.setInt32(shpI + 8, 1, true); // POINT=1 - shpView.setFloat64(shpI + 12, coords[0], true); // X - shpView.setFloat64(shpI + 20, coords[1], true); // Y + // For 3d (8 + 8 + 8) + 4 = 28 content length + shpView.setInt32(shpI + 8, (is3d ? types.geometries.POINTZ : types.geometries.POINT), true); // 4 + shpView.setFloat64(shpI + 12, coords[0], true); // X // 8 + shpView.setFloat64(shpI + 20, coords[1], true); // Y // 8 + if (is3d) { + shpView.setFloat64(shpI + 28, coords[2], true); // Z // 8 + shpView.setFloat64(shpI + 36, Math.pow(38, -10) - 1, true); // M // 8 + } // index shxView.setInt32(shxI, fileLength / 2); // length in 16-bit words - shxView.setInt32(shxI + 4, 10); + shxView.setInt32(shxI + 4, is3d ? 18 : 10); shxI += 8; shpI += contentLength; @@ -45,5 +52,9 @@ module.exports.shxLength = function(coordinates) { }; module.exports.shpLength = function(coordinates) { - return coordinates.length * 28; + // header: 8 bytes + shape type (4 bytes) + + // either 2 * 8 bytes (XY) or 4 * 8 bytes (XYZM) + return coordinates.reduce(function(length, coordinate) { + return length + coordinate.length === 3 ? 44 : 28; + }, 0); }; diff --git a/src/poly.js b/src/poly.js index 4ba8c2de..5665fac4 100644 --- a/src/poly.js +++ b/src/poly.js @@ -13,7 +13,15 @@ module.exports.write = function writePoints(geometries, extent, shpView, shxView var flattened = justCoords(coordinates), noParts = parts([coordinates], TYPE), - contentLength = (flattened.length * 16) + 48 + (noParts - 1) * 4; + contentLength = (flattened.length * 16) + 48 + (noParts - 1) * 4, + is3d = !!flattened[0][2]; + + // For a 3d shape the following additional information is present: + // ZMin, ZMax (8 bytes each) + // ZArray (8 bytes per point) + if (is3d) { + contentLength += 16 + flattened.length * 8; + } var featureExtent = flattened.reduce(function(extent, c) { return ext.enlarge(extent, c); @@ -21,7 +29,7 @@ module.exports.write = function writePoints(geometries, extent, shpView, shxView // INDEX shxView.setInt32(shxI, shxOffset / 2); // offset - shxView.setInt32(shxI + 4, contentLength / 2); // offset length + shxView.setInt32(shxI + 4, contentLength / 2); // content length shxI += 8; shxOffset += contentLength + 8; @@ -55,19 +63,39 @@ module.exports.write = function writePoints(geometries, extent, shpView, shxView ); } + // Points flattened.forEach(function writeLine(coords, i) { shpView.setFloat64(shpI + 56 + (i * 16) + (noParts - 1) * 4, coords[0], true); // X shpView.setFloat64(shpI + 56 + (i * 16) + (noParts - 1) * 4 + 8, coords[1], true); // Y }); + if (is3d) { + // Byte position Y as in spec + var y = (shpI + 56 + (i * 16) + (noParts - 1) * 4) + 16 * flattened.length; + + shpView.setFloat64(y, featureExtent.zmin, true); + shpView.setFloat64(y + 8, featureExtent.zmax, true); + flattened.forEach(function writeZArray(point, index) { + shpView.setFloat64(y + 16 + index * 8, point[2], true); + }); + } + shpI += contentLength + 8; } }; module.exports.shpLength = function(geometries) { + // NOTE: parts array length should not be included + // as this is calculated in write.js + + var flattened = justCoords(geometries), + is3d = !!flattened[0][2]; + + // geometry base length = 56 (up to points, including 8 byte record header) return (geometries.length * 56) + // points - (justCoords(geometries).length * 16); + (flattened.length * 16) + + (is3d ? 16 + flattened.length * 8 : 0); }; module.exports.shxLength = function(geometries) { @@ -82,7 +110,9 @@ module.exports.extent = function(coordinates) { function parts(geometries, TYPE) { var no = 1; - if (TYPE === types.geometries.POLYGON || TYPE === types.geometries.POLYLINE) { + if (TYPE === types.geometries.POLYGON || TYPE === types.geometries.POLYGONZ || + TYPE === types.geometries.POLYLINE || TYPE === types.geometries.POLYLINEZ) + { no = geometries.reduce(function (no, coords) { no += coords.length; if (Array.isArray(coords[0][0][0])) { // multi @@ -98,15 +128,9 @@ function parts(geometries, TYPE) { module.exports.parts = parts; -function totalPoints(geometries) { - var sum = 0; - geometries.forEach(function(g) { sum += g.length; }); - return sum; -} - function justCoords(coords, l) { if (l === undefined) l = []; - if (typeof coords[0][0] == 'object') { + if (typeof coords[0][0] === 'object') { return coords.reduce(function(memo, c) { return memo.concat(justCoords(c)); }, l); @@ -114,4 +138,3 @@ function justCoords(coords, l) { return coords; } } - diff --git a/src/write.js b/src/write.js index 0f0cbbe7..f4d679a1 100644 --- a/src/write.js +++ b/src/write.js @@ -8,9 +8,12 @@ var types = require('./types'), polyWriter = require('./poly'); var writers = { - 1: pointWriter, - 5: polyWriter, - 3: polyWriter + 1: pointWriter, // Point + 3: polyWriter, // PolyLine + 5: polyWriter, // Polygon + 11: pointWriter, // PointZ + 13: polyWriter, // PolyLineZ + 15: polyWriter // PolygonZ }; var recordHeaderLength = 8; @@ -65,4 +68,8 @@ function writeExtent(extent, view) { view.setFloat64(44, extent.ymin, true); view.setFloat64(52, extent.xmax, true); view.setFloat64(60, extent.ymax, true); + view.setFloat64(68, extent.zmin, true); + view.setFloat64(76, extent.zmax, true); + view.setFloat64(84, 0.0, true); // Measure not supported, so always set to 0 + view.setFloat64(92, 0.0, true); // Measure not supported, so always set to 0 } diff --git a/src/zip.js b/src/zip.js index b8e89d85..43c573bc 100644 --- a/src/zip.js +++ b/src/zip.js @@ -8,7 +8,7 @@ module.exports = function(gj, options) { var zip = new JSZip(), layers = zip.folder(options && options.folder ? options.folder : 'layers'); - [geojson.point(gj), geojson.line(gj), geojson.polygon(gj)] + [geojson.point(gj), geojson.pointZ(gj), geojson.line(gj), geojson.lineZ(gj), geojson.polygon(gj), geojson.polygonZ(gj)] .forEach(function(l) { if (l.geometries.length && l.geometries[0].length) { write(