Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support geojson with altitude #140

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
85 changes: 58 additions & 27 deletions src/clip.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,15 @@ import createFeature from './feature.js';
* / | \____|____/
* | |
*
* k1 and k2 are the line coordinates
* axis: 0 for x, 1 for y
* minAll and maxAll: minimum and maximum coordinate value for all features
*/
* @param {*} features features to be clipped
* @param {*} scale scaling of the axis
* @param {*} k1 left/top edge of the given axis
* @param {*} k2 right/bottom edge of the given axis
* @param {*} axis 0 = x, 1 = y
* @param {*} minAll minimum value on the given axis for all features
* @param {*} maxAll maximum value on the given axis for all features
* @param {*} options
*/
export default function clip(features, scale, k1, k2, axis, minAll, maxAll, options) {
k1 /= scale;
k2 /= scale;
Expand All @@ -22,6 +27,7 @@ export default function clip(features, scale, k1, k2, axis, minAll, maxAll, opti

for (const feature of features) {
const geometry = feature.geometry;
const sqDist = feature.sqDist;
let type = feature.type;

const min = axis === 0 ? feature.minX : feature.minY;
Expand All @@ -35,33 +41,40 @@ export default function clip(features, scale, k1, k2, axis, minAll, maxAll, opti
}

let newGeometry = [];
let newSqDist = [];

if (type === 'Point' || type === 'MultiPoint') {
clipPoints(geometry, newGeometry, k1, k2, axis);

} else if (type === 'LineString') {
clipLine(geometry, newGeometry, k1, k2, axis, false, options.lineMetrics);
clipLine(geometry, newGeometry, sqDist, newSqDist, k1, k2, axis, false, options.lineMetrics);

} else if (type === 'MultiLineString') {
clipLines(geometry, newGeometry, k1, k2, axis, false);
clipLines(geometry, newGeometry, sqDist, newSqDist, k1, k2, axis, false);

} else if (type === 'Polygon') {
clipLines(geometry, newGeometry, k1, k2, axis, true);
clipLines(geometry, newGeometry, sqDist, newSqDist, k1, k2, axis, true);

} else if (type === 'MultiPolygon') {
for (const polygon of geometry) {
for (let i = 0; i < geometry.length; ++i) {
const polygon = geometry[i];
const sqDistPoly = sqDist[i];
const newPolygon = [];
clipLines(polygon, newPolygon, k1, k2, axis, true);
const newSqDistPoly = [];
clipLines(polygon, newPolygon, sqDistPoly, newSqDistPoly, k1, k2, axis, true);
if (newPolygon.length) {
newGeometry.push(newPolygon);
newSqDist.push(newSqDistPoly);
}
}
}

if (newGeometry.length) {
if (options.lineMetrics && type === 'LineString') {
for (const line of newGeometry) {
clipped.push(createFeature(feature.id, type, line, feature.tags));
for (let i = 0; i < newGeometry.length; ++i) {
const line = newGeometry[i];
const lineSqDist = newSqDist[i];
clipped.push(createFeature(feature.id, type, line, lineSqDist, feature.tags));
}
continue;
}
Expand All @@ -70,6 +83,7 @@ export default function clip(features, scale, k1, k2, axis, minAll, maxAll, opti
if (newGeometry.length === 1) {
type = 'LineString';
newGeometry = newGeometry[0];
newSqDist = newSqDist[0];
} else {
type = 'MultiLineString';
}
Expand All @@ -78,7 +92,7 @@ export default function clip(features, scale, k1, k2, axis, minAll, maxAll, opti
type = newGeometry.length === 3 ? 'Point' : 'MultiPoint';
}

clipped.push(createFeature(feature.id, type, newGeometry, feature.tags));
clipped.push(createFeature(feature.id, type, newGeometry, newSqDist, feature.tags));
}
}

Expand All @@ -95,55 +109,65 @@ function clipPoints(geom, newGeom, k1, k2, axis) {
}
}

function clipLine(geom, newGeom, k1, k2, axis, isPolygon, trackMetrics) {
function clipLine(geom, newGeom, sqDist, newSqDist, k1, k2, axis, isPolygon, trackMetrics) {

let slice = newSlice(geom);
let sqDistSlice = [];
const intersect = axis === 0 ? intersectX : intersectY;
let len = geom.start;
let segLen, t;

for (let i = 0; i < geom.length - 3; i += 3) {
const s = sqDist[i / 3];
const ax = geom[i];
const ay = geom[i + 1];
const az = geom[i + 2];
const bx = geom[i + 3];
const by = geom[i + 4];
const bz = geom[i + 5];
const a = axis === 0 ? ax : ay;
const b = axis === 0 ? bx : by;
let exited = false;

if (trackMetrics) segLen = Math.sqrt(Math.pow(ax - bx, 2) + Math.pow(ay - by, 2));

if (a < k1) {
// ---|--> | (line enters the clip region from the left)
// ---|--> | (line enters the clip region from the left/top)
if (b > k1) {
t = intersect(slice, ax, ay, bx, by, k1);
t = intersect(slice, ax, ay, az, bx, by, bz, k1);
if (trackMetrics) slice.start = len + segLen * t;
sqDistSlice.push(1);
}
} else if (a > k2) {
// | <--|--- (line enters the clip region from the right)
// | <--|--- (line enters the clip region from the right/bottom)
if (b < k2) {
t = intersect(slice, ax, ay, bx, by, k2);
t = intersect(slice, ax, ay, az, bx, by, bz, k2);
if (trackMetrics) slice.start = len + segLen * t;
sqDistSlice.push(1);
}
} else {
addPoint(slice, ax, ay, az);
sqDistSlice.push(s);
}
if (b < k1 && a >= k1) {
// <--|--- | or <--|-----|--- (line exits the clip region on the left)
t = intersect(slice, ax, ay, bx, by, k1);
t = intersect(slice, ax, ay, az, bx, by, bz, k1);
sqDistSlice.push(1);
exited = true;
}
if (b > k2 && a <= k2) {
// | ---|--> or ---|-----|--> (line exits the clip region on the right)
t = intersect(slice, ax, ay, bx, by, k2);
t = intersect(slice, ax, ay, az, bx, by, bz, k2);
sqDistSlice.push(1);
exited = true;
}

if (!isPolygon && exited) {
if (trackMetrics) slice.end = len + segLen * t;
newGeom.push(slice);
newSqDist.push(sqDistSlice);
slice = newSlice(geom);
sqDistSlice = [];
}

if (trackMetrics) len += segLen;
Expand All @@ -155,17 +179,22 @@ function clipLine(geom, newGeom, k1, k2, axis, isPolygon, trackMetrics) {
const ay = geom[last + 1];
const az = geom[last + 2];
const a = axis === 0 ? ax : ay;
if (a >= k1 && a <= k2) addPoint(slice, ax, ay, az);
if (a >= k1 && a <= k2) {
addPoint(slice, ax, ay, az);
sqDistSlice.push(1);
}

// close the polygon if its endpoints are not the same after clipping
last = slice.length - 3;
if (isPolygon && last >= 3 && (slice[last] !== slice[0] || slice[last + 1] !== slice[1])) {
addPoint(slice, slice[0], slice[1], slice[2]);
sqDistSlice.push(1);
}

// add the final slice
if (slice.length) {
newGeom.push(slice);
newSqDist.push(sqDistSlice);
}
}

Expand All @@ -177,24 +206,26 @@ function newSlice(line) {
return slice;
}

function clipLines(geom, newGeom, k1, k2, axis, isPolygon) {
for (const line of geom) {
clipLine(line, newGeom, k1, k2, axis, isPolygon, false);
function clipLines(geom, newGeom, sqDist, newSqDist, k1, k2, axis, isPolygon) {
for (let i = 0; i < geom.length; ++i) {
const lineGeom = geom[i];
const lineSqDist = sqDist[i];
clipLine(lineGeom, newGeom, lineSqDist, newSqDist, k1, k2, axis, isPolygon, false);
}
}

function addPoint(out, x, y, z) {
out.push(x, y, z);
}

function intersectX(out, ax, ay, bx, by, x) {
function intersectX(out, ax, ay, az, bx, by, bz, x) {
const t = (x - ax) / (bx - ax);
addPoint(out, x, ay + (by - ay) * t, 1);
addPoint(out, x, ay + (by - ay) * t, az + (bz - az) * t);
return t;
}

function intersectY(out, ax, ay, bx, by, y) {
function intersectY(out, ax, ay, az, bx, by, bz, y) {
const t = (y - ay) / (by - ay);
addPoint(out, ax + (bx - ax) * t, y, 1);
addPoint(out, ax + (bx - ax) * t, y, az + (bz - az) * t);
return t;
}
47 changes: 33 additions & 14 deletions src/convert.js
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ function convertFeature(features, geojson, options, index) {
const type = geojson.geometry.type;
const tolerance = Math.pow(options.tolerance / ((1 << options.maxZoom) * options.extent), 2);
let geometry = [];
let sqDist = []; // squared distances for simplification
let id = geojson.id;
if (options.promoteId) {
id = geojson.properties[options.promoteId];
Expand All @@ -44,29 +45,32 @@ function convertFeature(features, geojson, options, index) {
}

} else if (type === 'LineString') {
convertLine(coords, geometry, tolerance, false);
convertLine(coords, geometry, sqDist, tolerance, false);

} else if (type === 'MultiLineString') {
if (options.lineMetrics) {
// explode into linestrings to be able to track metrics
for (const line of coords) {
geometry = [];
convertLine(line, geometry, tolerance, false);
sqDist = [];
convertLine(line, geometry, sqDist, tolerance, false);
features.push(createFeature(id, 'LineString', geometry, geojson.properties));
}
return;
} else {
convertLines(coords, geometry, tolerance, false);
convertLines(coords, geometry, sqDist, tolerance, false);
}

} else if (type === 'Polygon') {
convertLines(coords, geometry, tolerance, true);
convertLines(coords, geometry, sqDist, tolerance, true);

} else if (type === 'MultiPolygon') {
for (const polygon of coords) {
const newPolygon = [];
convertLines(polygon, newPolygon, tolerance, true);
const newSqDist = [];
convertLines(polygon, newPolygon, newSqDist, tolerance, true);
geometry.push(newPolygon);
sqDist.push(newSqDist);
}
} else if (type === 'GeometryCollection') {
for (const singleGeometry of geojson.geometry.geometries) {
Expand All @@ -81,22 +85,33 @@ function convertFeature(features, geojson, options, index) {
throw new Error('Input data is not a valid GeoJSON object.');
}

features.push(createFeature(id, type, geometry, geojson.properties));
features.push(createFeature(id, type, geometry, sqDist, geojson.properties));
}

function convertPoint(coords, out) {
out.push(projectX(coords[0]), projectY(coords[1]), 0);
out.push(projectX(coords[0]), projectY(coords[1]));

if (coords.length > 2) {
out.push(coords[2]);
} else {
out.push(0);
}
}

function convertLine(ring, out, tolerance, isPolygon) {
function convertLine(ring, out, sqDist, tolerance, isPolygon) {
let x0, y0;
let size = 0;

for (let j = 0; j < ring.length; j++) {
const x = projectX(ring[j][0]);
const y = projectY(ring[j][1]);

out.push(x, y, 0);
out.push(x, y);
if (ring[j].length > 2) {
out.push(ring[j][2]);
} else {
out.push(0);
}

if (j > 0) {
if (isPolygon) {
Expand All @@ -110,20 +125,24 @@ function convertLine(ring, out, tolerance, isPolygon) {
}

const last = out.length - 3;
out[2] = 1;
simplify(out, 0, last, tolerance);
out[last + 2] = 1;
sqDist.length = out.length / 3;
sqDist.fill(0);
sqDist[0] = 1;
simplify(sqDist, out, 0, last, tolerance);
sqDist[last / 3] = 1;

out.size = Math.abs(size);
out.start = 0;
out.end = out.size;
}

function convertLines(rings, out, tolerance, isPolygon) {
function convertLines(rings, out, outSqDist, tolerance, isPolygon) {
for (let i = 0; i < rings.length; i++) {
const geom = [];
convertLine(rings[i], geom, tolerance, isPolygon);
const sqDist = [];
convertLine(rings[i], geom, sqDist, tolerance, isPolygon);
out.push(geom);
outSqDist.push(sqDist);
}
}

Expand Down
3 changes: 2 additions & 1 deletion src/feature.js
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@

export default function createFeature(id, type, geom, tags) {
export default function createFeature(id, type, geom, sqDist, tags) {
const feature = {
id: id == null ? null : id,
type,
geometry: geom,
sqDist,
tags,
minX: Infinity,
minY: Infinity,
Expand Down
Loading