|
|
/**
|
|
|
@license
|
|
|
topojson - https://github.com/topojson/topojson
|
|
|
|
|
|
Copyright (c) 2012-2016, Michael Bostock
|
|
|
All rights reserved.
|
|
|
|
|
|
Redistribution and use in source and binary forms, with or without
|
|
|
modification, are permitted provided that the following conditions are met:
|
|
|
|
|
|
* Redistributions of source code must retain the above copyright notice, this
|
|
|
list of conditions and the following disclaimer.
|
|
|
|
|
|
* Redistributions in binary form must reproduce the above copyright notice,
|
|
|
this list of conditions and the following disclaimer in the documentation
|
|
|
and/or other materials provided with the distribution.
|
|
|
|
|
|
* The name Michael Bostock may not be used to endorse or promote products
|
|
|
derived from this software without specific prior written permission.
|
|
|
|
|
|
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
|
|
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
|
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
|
|
DISCLAIMED. IN NO EVENT SHALL MICHAEL BOSTOCK BE LIABLE FOR ANY DIRECT,
|
|
|
INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
|
|
|
BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
|
|
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
|
|
|
OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
|
|
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
|
|
|
EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
|
**/
|
|
|
|
|
|
var tmp = {};
|
|
|
|
|
|
// https://github.com/topojson/topojson Version 3.0.2. Copyright 2017 Mike Bostock.
|
|
|
(function (global, factory) {
|
|
|
typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports) :
|
|
|
(factory((global.topojson = global.topojson || {})));
|
|
|
}(tmp, (function (exports) { 'use strict';
|
|
|
|
|
|
var identity = function(x) {
|
|
|
return x;
|
|
|
};
|
|
|
|
|
|
var transform = function(transform) {
|
|
|
if (transform == null) return identity;
|
|
|
var x0,
|
|
|
y0,
|
|
|
kx = transform.scale[0],
|
|
|
ky = transform.scale[1],
|
|
|
dx = transform.translate[0],
|
|
|
dy = transform.translate[1];
|
|
|
return function(input, i) {
|
|
|
if (!i) x0 = y0 = 0;
|
|
|
var j = 2, n = input.length, output = new Array(n);
|
|
|
output[0] = (x0 += input[0]) * kx + dx;
|
|
|
output[1] = (y0 += input[1]) * ky + dy;
|
|
|
while (j < n) output[j] = input[j], ++j;
|
|
|
return output;
|
|
|
};
|
|
|
};
|
|
|
|
|
|
var bbox = function(topology) {
|
|
|
var t = transform(topology.transform), key,
|
|
|
x0 = Infinity, y0 = x0, x1 = -x0, y1 = -x0;
|
|
|
|
|
|
function bboxPoint(p) {
|
|
|
p = t(p);
|
|
|
if (p[0] < x0) x0 = p[0];
|
|
|
if (p[0] > x1) x1 = p[0];
|
|
|
if (p[1] < y0) y0 = p[1];
|
|
|
if (p[1] > y1) y1 = p[1];
|
|
|
}
|
|
|
|
|
|
function bboxGeometry(o) {
|
|
|
switch (o.type) {
|
|
|
case "GeometryCollection": o.geometries.forEach(bboxGeometry); break;
|
|
|
case "Point": bboxPoint(o.coordinates); break;
|
|
|
case "MultiPoint": o.coordinates.forEach(bboxPoint); break;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
topology.arcs.forEach(function(arc) {
|
|
|
var i = -1, n = arc.length, p;
|
|
|
while (++i < n) {
|
|
|
p = t(arc[i], i);
|
|
|
if (p[0] < x0) x0 = p[0];
|
|
|
if (p[0] > x1) x1 = p[0];
|
|
|
if (p[1] < y0) y0 = p[1];
|
|
|
if (p[1] > y1) y1 = p[1];
|
|
|
}
|
|
|
});
|
|
|
|
|
|
for (key in topology.objects) {
|
|
|
bboxGeometry(topology.objects[key]);
|
|
|
}
|
|
|
|
|
|
return [x0, y0, x1, y1];
|
|
|
};
|
|
|
|
|
|
var reverse = function(array, n) {
|
|
|
var t, j = array.length, i = j - n;
|
|
|
while (i < --j) t = array[i], array[i++] = array[j], array[j] = t;
|
|
|
};
|
|
|
|
|
|
var feature = function(topology, o) {
|
|
|
return o.type === "GeometryCollection"
|
|
|
? {type: "FeatureCollection", features: o.geometries.map(function(o) { return feature$1(topology, o); })}
|
|
|
: feature$1(topology, o);
|
|
|
};
|
|
|
|
|
|
function feature$1(topology, o) {
|
|
|
var id = o.id,
|
|
|
bbox = o.bbox,
|
|
|
properties = o.properties == null ? {} : o.properties,
|
|
|
geometry = object(topology, o);
|
|
|
return id == null && bbox == null ? {type: "Feature", properties: properties, geometry: geometry}
|
|
|
: bbox == null ? {type: "Feature", id: id, properties: properties, geometry: geometry}
|
|
|
: {type: "Feature", id: id, bbox: bbox, properties: properties, geometry: geometry};
|
|
|
}
|
|
|
|
|
|
function object(topology, o) {
|
|
|
var transformPoint = transform(topology.transform),
|
|
|
arcs = topology.arcs;
|
|
|
|
|
|
function arc(i, points) {
|
|
|
if (points.length) points.pop();
|
|
|
for (var a = arcs[i < 0 ? ~i : i], k = 0, n = a.length; k < n; ++k) {
|
|
|
points.push(transformPoint(a[k], k));
|
|
|
}
|
|
|
if (i < 0) reverse(points, n);
|
|
|
}
|
|
|
|
|
|
function point(p) {
|
|
|
return transformPoint(p);
|
|
|
}
|
|
|
|
|
|
function line(arcs) {
|
|
|
var points = [];
|
|
|
for (var i = 0, n = arcs.length; i < n; ++i) arc(arcs[i], points);
|
|
|
if (points.length < 2) points.push(points[0]); // This should never happen per the specification.
|
|
|
return points;
|
|
|
}
|
|
|
|
|
|
function ring(arcs) {
|
|
|
var points = line(arcs);
|
|
|
while (points.length < 4) points.push(points[0]); // This may happen if an arc has only two points.
|
|
|
return points;
|
|
|
}
|
|
|
|
|
|
function polygon(arcs) {
|
|
|
return arcs.map(ring);
|
|
|
}
|
|
|
|
|
|
function geometry(o) {
|
|
|
var type = o.type, coordinates;
|
|
|
switch (type) {
|
|
|
case "GeometryCollection": return {type: type, geometries: o.geometries.map(geometry)};
|
|
|
case "Point": coordinates = point(o.coordinates); break;
|
|
|
case "MultiPoint": coordinates = o.coordinates.map(point); break;
|
|
|
case "LineString": coordinates = line(o.arcs); break;
|
|
|
case "MultiLineString": coordinates = o.arcs.map(line); break;
|
|
|
case "Polygon": coordinates = polygon(o.arcs); break;
|
|
|
case "MultiPolygon": coordinates = o.arcs.map(polygon); break;
|
|
|
default: return null;
|
|
|
}
|
|
|
return {type: type, coordinates: coordinates};
|
|
|
}
|
|
|
|
|
|
return geometry(o);
|
|
|
}
|
|
|
|
|
|
var stitch = function(topology, arcs) {
|
|
|
var stitchedArcs = {},
|
|
|
fragmentByStart = {},
|
|
|
fragmentByEnd = {},
|
|
|
fragments = [],
|
|
|
emptyIndex = -1;
|
|
|
|
|
|
// Stitch empty arcs first, since they may be subsumed by other arcs.
|
|
|
arcs.forEach(function(i, j) {
|
|
|
var arc = topology.arcs[i < 0 ? ~i : i], t;
|
|
|
if (arc.length < 3 && !arc[1][0] && !arc[1][1]) {
|
|
|
t = arcs[++emptyIndex], arcs[emptyIndex] = i, arcs[j] = t;
|
|
|
}
|
|
|
});
|
|
|
|
|
|
arcs.forEach(function(i) {
|
|
|
var e = ends(i),
|
|
|
start = e[0],
|
|
|
end = e[1],
|
|
|
f, g;
|
|
|
|
|
|
if (f = fragmentByEnd[start]) {
|
|
|
delete fragmentByEnd[f.end];
|
|
|
f.push(i);
|
|
|
f.end = end;
|
|
|
if (g = fragmentByStart[end]) {
|
|
|
delete fragmentByStart[g.start];
|
|
|
var fg = g === f ? f : f.concat(g);
|
|
|
fragmentByStart[fg.start = f.start] = fragmentByEnd[fg.end = g.end] = fg;
|
|
|
} else {
|
|
|
fragmentByStart[f.start] = fragmentByEnd[f.end] = f;
|
|
|
}
|
|
|
} else if (f = fragmentByStart[end]) {
|
|
|
delete fragmentByStart[f.start];
|
|
|
f.unshift(i);
|
|
|
f.start = start;
|
|
|
if (g = fragmentByEnd[start]) {
|
|
|
delete fragmentByEnd[g.end];
|
|
|
var gf = g === f ? f : g.concat(f);
|
|
|
fragmentByStart[gf.start = g.start] = fragmentByEnd[gf.end = f.end] = gf;
|
|
|
} else {
|
|
|
fragmentByStart[f.start] = fragmentByEnd[f.end] = f;
|
|
|
}
|
|
|
} else {
|
|
|
f = [i];
|
|
|
fragmentByStart[f.start = start] = fragmentByEnd[f.end = end] = f;
|
|
|
}
|
|
|
});
|
|
|
|
|
|
function ends(i) {
|
|
|
var arc = topology.arcs[i < 0 ? ~i : i], p0 = arc[0], p1;
|
|
|
if (topology.transform) p1 = [0, 0], arc.forEach(function(dp) { p1[0] += dp[0], p1[1] += dp[1]; });
|
|
|
else p1 = arc[arc.length - 1];
|
|
|
return i < 0 ? [p1, p0] : [p0, p1];
|
|
|
}
|
|
|
|
|
|
function flush(fragmentByEnd, fragmentByStart) {
|
|
|
for (var k in fragmentByEnd) {
|
|
|
var f = fragmentByEnd[k];
|
|
|
delete fragmentByStart[f.start];
|
|
|
delete f.start;
|
|
|
delete f.end;
|
|
|
f.forEach(function(i) { stitchedArcs[i < 0 ? ~i : i] = 1; });
|
|
|
fragments.push(f);
|
|
|
}
|
|
|
}
|
|
|
|
|
|
flush(fragmentByEnd, fragmentByStart);
|
|
|
flush(fragmentByStart, fragmentByEnd);
|
|
|
arcs.forEach(function(i) { if (!stitchedArcs[i < 0 ? ~i : i]) fragments.push([i]); });
|
|
|
|
|
|
return fragments;
|
|
|
};
|
|
|
|
|
|
var mesh = function(topology) {
|
|
|
return object(topology, meshArcs.apply(this, arguments));
|
|
|
};
|
|
|
|
|
|
function meshArcs(topology, object$$1, filter) {
|
|
|
var arcs, i, n;
|
|
|
if (arguments.length > 1) arcs = extractArcs(topology, object$$1, filter);
|
|
|
else for (i = 0, arcs = new Array(n = topology.arcs.length); i < n; ++i) arcs[i] = i;
|
|
|
return {type: "MultiLineString", arcs: stitch(topology, arcs)};
|
|
|
}
|
|
|
|
|
|
function extractArcs(topology, object$$1, filter) {
|
|
|
var arcs = [],
|
|
|
geomsByArc = [],
|
|
|
geom;
|
|
|
|
|
|
function extract0(i) {
|
|
|
var j = i < 0 ? ~i : i;
|
|
|
(geomsByArc[j] || (geomsByArc[j] = [])).push({i: i, g: geom});
|
|
|
}
|
|
|
|
|
|
function extract1(arcs) {
|
|
|
arcs.forEach(extract0);
|
|
|
}
|
|
|
|
|
|
function extract2(arcs) {
|
|
|
arcs.forEach(extract1);
|
|
|
}
|
|
|
|
|
|
function extract3(arcs) {
|
|
|
arcs.forEach(extract2);
|
|
|
}
|
|
|
|
|
|
function geometry(o) {
|
|
|
switch (geom = o, o.type) {
|
|
|
case "GeometryCollection": o.geometries.forEach(geometry); break;
|
|
|
case "LineString": extract1(o.arcs); break;
|
|
|
case "MultiLineString": case "Polygon": extract2(o.arcs); break;
|
|
|
case "MultiPolygon": extract3(o.arcs); break;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
geometry(object$$1);
|
|
|
|
|
|
geomsByArc.forEach(filter == null
|
|
|
? function(geoms) { arcs.push(geoms[0].i); }
|
|
|
: function(geoms) { if (filter(geoms[0].g, geoms[geoms.length - 1].g)) arcs.push(geoms[0].i); });
|
|
|
|
|
|
return arcs;
|
|
|
}
|
|
|
|
|
|
function planarRingArea(ring) {
|
|
|
var i = -1, n = ring.length, a, b = ring[n - 1], area = 0;
|
|
|
while (++i < n) a = b, b = ring[i], area += a[0] * b[1] - a[1] * b[0];
|
|
|
return Math.abs(area); // Note: doubled area!
|
|
|
}
|
|
|
|
|
|
var merge = function(topology) {
|
|
|
return object(topology, mergeArcs.apply(this, arguments));
|
|
|
};
|
|
|
|
|
|
function mergeArcs(topology, objects) {
|
|
|
var polygonsByArc = {},
|
|
|
polygons = [],
|
|
|
groups = [];
|
|
|
|
|
|
objects.forEach(geometry);
|
|
|
|
|
|
function geometry(o) {
|
|
|
switch (o.type) {
|
|
|
case "GeometryCollection": o.geometries.forEach(geometry); break;
|
|
|
case "Polygon": extract(o.arcs); break;
|
|
|
case "MultiPolygon": o.arcs.forEach(extract); break;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
function extract(polygon) {
|
|
|
polygon.forEach(function(ring) {
|
|
|
ring.forEach(function(arc) {
|
|
|
(polygonsByArc[arc = arc < 0 ? ~arc : arc] || (polygonsByArc[arc] = [])).push(polygon);
|
|
|
});
|
|
|
});
|
|
|
polygons.push(polygon);
|
|
|
}
|
|
|
|
|
|
function area(ring) {
|
|
|
return planarRingArea(object(topology, {type: "Polygon", arcs: [ring]}).coordinates[0]);
|
|
|
}
|
|
|
|
|
|
polygons.forEach(function(polygon) {
|
|
|
if (!polygon._) {
|
|
|
var group = [],
|
|
|
neighbors = [polygon];
|
|
|
polygon._ = 1;
|
|
|
groups.push(group);
|
|
|
while (polygon = neighbors.pop()) {
|
|
|
group.push(polygon);
|
|
|
polygon.forEach(function(ring) {
|
|
|
ring.forEach(function(arc) {
|
|
|
polygonsByArc[arc < 0 ? ~arc : arc].forEach(function(polygon) {
|
|
|
if (!polygon._) {
|
|
|
polygon._ = 1;
|
|
|
neighbors.push(polygon);
|
|
|
}
|
|
|
});
|
|
|
});
|
|
|
});
|
|
|
}
|
|
|
}
|
|
|
});
|
|
|
|
|
|
polygons.forEach(function(polygon) {
|
|
|
delete polygon._;
|
|
|
});
|
|
|
|
|
|
return {
|
|
|
type: "MultiPolygon",
|
|
|
arcs: groups.map(function(polygons) {
|
|
|
var arcs = [], n;
|
|
|
|
|
|
// Extract the exterior (unique) arcs.
|
|
|
polygons.forEach(function(polygon) {
|
|
|
polygon.forEach(function(ring) {
|
|
|
ring.forEach(function(arc) {
|
|
|
if (polygonsByArc[arc < 0 ? ~arc : arc].length < 2) {
|
|
|
arcs.push(arc);
|
|
|
}
|
|
|
});
|
|
|
});
|
|
|
});
|
|
|
|
|
|
// Stitch the arcs into one or more rings.
|
|
|
arcs = stitch(topology, arcs);
|
|
|
|
|
|
// If more than one ring is returned,
|
|
|
// at most one of these rings can be the exterior;
|
|
|
// choose the one with the greatest absolute area.
|
|
|
if ((n = arcs.length) > 1) {
|
|
|
for (var i = 1, k = area(arcs[0]), ki, t; i < n; ++i) {
|
|
|
if ((ki = area(arcs[i])) > k) {
|
|
|
t = arcs[0], arcs[0] = arcs[i], arcs[i] = t, k = ki;
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
|
|
|
return arcs;
|
|
|
})
|
|
|
};
|
|
|
}
|
|
|
|
|
|
var bisect = function(a, x) {
|
|
|
var lo = 0, hi = a.length;
|
|
|
while (lo < hi) {
|
|
|
var mid = lo + hi >>> 1;
|
|
|
if (a[mid] < x) lo = mid + 1;
|
|
|
else hi = mid;
|
|
|
}
|
|
|
return lo;
|
|
|
};
|
|
|
|
|
|
var neighbors = function(objects) {
|
|
|
var indexesByArc = {}, // arc index -> array of object indexes
|
|
|
neighbors = objects.map(function() { return []; });
|
|
|
|
|
|
function line(arcs, i) {
|
|
|
arcs.forEach(function(a) {
|
|
|
if (a < 0) a = ~a;
|
|
|
var o = indexesByArc[a];
|
|
|
if (o) o.push(i);
|
|
|
else indexesByArc[a] = [i];
|
|
|
});
|
|
|
}
|
|
|
|
|
|
function polygon(arcs, i) {
|
|
|
arcs.forEach(function(arc) { line(arc, i); });
|
|
|
}
|
|
|
|
|
|
function geometry(o, i) {
|
|
|
if (o.type === "GeometryCollection") o.geometries.forEach(function(o) { geometry(o, i); });
|
|
|
else if (o.type in geometryType) geometryType[o.type](o.arcs, i);
|
|
|
}
|
|
|
|
|
|
var geometryType = {
|
|
|
LineString: line,
|
|
|
MultiLineString: polygon,
|
|
|
Polygon: polygon,
|
|
|
MultiPolygon: function(arcs, i) { arcs.forEach(function(arc) { polygon(arc, i); }); }
|
|
|
};
|
|
|
|
|
|
objects.forEach(geometry);
|
|
|
|
|
|
for (var i in indexesByArc) {
|
|
|
for (var indexes = indexesByArc[i], m = indexes.length, j = 0; j < m; ++j) {
|
|
|
for (var k = j + 1; k < m; ++k) {
|
|
|
var ij = indexes[j], ik = indexes[k], n;
|
|
|
if ((n = neighbors[ij])[i = bisect(n, ik)] !== ik) n.splice(i, 0, ik);
|
|
|
if ((n = neighbors[ik])[i = bisect(n, ij)] !== ij) n.splice(i, 0, ij);
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
|
|
|
return neighbors;
|
|
|
};
|
|
|
|
|
|
var untransform = function(transform) {
|
|
|
if (transform == null) return identity;
|
|
|
var x0,
|
|
|
y0,
|
|
|
kx = transform.scale[0],
|
|
|
ky = transform.scale[1],
|
|
|
dx = transform.translate[0],
|
|
|
dy = transform.translate[1];
|
|
|
return function(input, i) {
|
|
|
if (!i) x0 = y0 = 0;
|
|
|
var j = 2,
|
|
|
n = input.length,
|
|
|
output = new Array(n),
|
|
|
x1 = Math.round((input[0] - dx) / kx),
|
|
|
y1 = Math.round((input[1] - dy) / ky);
|
|
|
output[0] = x1 - x0, x0 = x1;
|
|
|
output[1] = y1 - y0, y0 = y1;
|
|
|
while (j < n) output[j] = input[j], ++j;
|
|
|
return output;
|
|
|
};
|
|
|
};
|
|
|
|
|
|
var quantize = function(topology, transform) {
|
|
|
if (topology.transform) throw new Error("already quantized");
|
|
|
|
|
|
if (!transform || !transform.scale) {
|
|
|
if (!((n = Math.floor(transform)) >= 2)) throw new Error("n must be \u22652");
|
|
|
box = topology.bbox || bbox(topology);
|
|
|
var x0 = box[0], y0 = box[1], x1 = box[2], y1 = box[3], n;
|
|
|
transform = {scale: [x1 - x0 ? (x1 - x0) / (n - 1) : 1, y1 - y0 ? (y1 - y0) / (n - 1) : 1], translate: [x0, y0]};
|
|
|
} else {
|
|
|
box = topology.bbox;
|
|
|
}
|
|
|
|
|
|
var t = untransform(transform), box, key, inputs = topology.objects, outputs = {};
|
|
|
|
|
|
function quantizePoint(point) {
|
|
|
return t(point);
|
|
|
}
|
|
|
|
|
|
function quantizeGeometry(input) {
|
|
|
var output;
|
|
|
switch (input.type) {
|
|
|
case "GeometryCollection": output = {type: "GeometryCollection", geometries: input.geometries.map(quantizeGeometry)}; break;
|
|
|
case "Point": output = {type: "Point", coordinates: quantizePoint(input.coordinates)}; break;
|
|
|
case "MultiPoint": output = {type: "MultiPoint", coordinates: input.coordinates.map(quantizePoint)}; break;
|
|
|
default: return input;
|
|
|
}
|
|
|
if (input.id != null) output.id = input.id;
|
|
|
if (input.bbox != null) output.bbox = input.bbox;
|
|
|
if (input.properties != null) output.properties = input.properties;
|
|
|
return output;
|
|
|
}
|
|
|
|
|
|
function quantizeArc(input) {
|
|
|
var i = 0, j = 1, n = input.length, p, output = new Array(n); // pessimistic
|
|
|
output[0] = t(input[0], 0);
|
|
|
while (++i < n) if ((p = t(input[i], i))[0] || p[1]) output[j++] = p; // non-coincident points
|
|
|
if (j === 1) output[j++] = [0, 0]; // an arc must have at least two points
|
|
|
output.length = j;
|
|
|
return output;
|
|
|
}
|
|
|
|
|
|
for (key in inputs) outputs[key] = quantizeGeometry(inputs[key]);
|
|
|
|
|
|
return {
|
|
|
type: "Topology",
|
|
|
bbox: box,
|
|
|
transform: transform,
|
|
|
objects: outputs,
|
|
|
arcs: topology.arcs.map(quantizeArc)
|
|
|
};
|
|
|
};
|
|
|
|
|
|
// Computes the bounding box of the specified hash of GeoJSON objects.
|
|
|
var bounds = function(objects) {
|
|
|
var x0 = Infinity,
|
|
|
y0 = Infinity,
|
|
|
x1 = -Infinity,
|
|
|
y1 = -Infinity;
|
|
|
|
|
|
function boundGeometry(geometry) {
|
|
|
if (geometry != null && boundGeometryType.hasOwnProperty(geometry.type)) boundGeometryType[geometry.type](geometry);
|
|
|
}
|
|
|
|
|
|
var boundGeometryType = {
|
|
|
GeometryCollection: function(o) { o.geometries.forEach(boundGeometry); },
|
|
|
Point: function(o) { boundPoint(o.coordinates); },
|
|
|
MultiPoint: function(o) { o.coordinates.forEach(boundPoint); },
|
|
|
LineString: function(o) { boundLine(o.arcs); },
|
|
|
MultiLineString: function(o) { o.arcs.forEach(boundLine); },
|
|
|
Polygon: function(o) { o.arcs.forEach(boundLine); },
|
|
|
MultiPolygon: function(o) { o.arcs.forEach(boundMultiLine); }
|
|
|
};
|
|
|
|
|
|
function boundPoint(coordinates) {
|
|
|
var x = coordinates[0],
|
|
|
y = coordinates[1];
|
|
|
if (x < x0) x0 = x;
|
|
|
if (x > x1) x1 = x;
|
|
|
if (y < y0) y0 = y;
|
|
|
if (y > y1) y1 = y;
|
|
|
}
|
|
|
|
|
|
function boundLine(coordinates) {
|
|
|
coordinates.forEach(boundPoint);
|
|
|
}
|
|
|
|
|
|
function boundMultiLine(coordinates) {
|
|
|
coordinates.forEach(boundLine);
|
|
|
}
|
|
|
|
|
|
for (var key in objects) {
|
|
|
boundGeometry(objects[key]);
|
|
|
}
|
|
|
|
|
|
return x1 >= x0 && y1 >= y0 ? [x0, y0, x1, y1] : undefined;
|
|
|
};
|
|
|
|
|
|
var hashset = function(size, hash, equal, type, empty) {
|
|
|
if (arguments.length === 3) {
|
|
|
type = Array;
|
|
|
empty = null;
|
|
|
}
|
|
|
|
|
|
var store = new type(size = 1 << Math.max(4, Math.ceil(Math.log(size) / Math.LN2))),
|
|
|
mask = size - 1;
|
|
|
|
|
|
for (var i = 0; i < size; ++i) {
|
|
|
store[i] = empty;
|
|
|
}
|
|
|
|
|
|
function add(value) {
|
|
|
var index = hash(value) & mask,
|
|
|
match = store[index],
|
|
|
collisions = 0;
|
|
|
while (match != empty) {
|
|
|
if (equal(match, value)) return true;
|
|
|
if (++collisions >= size) throw new Error("full hashset");
|
|
|
match = store[index = (index + 1) & mask];
|
|
|
}
|
|
|
store[index] = value;
|
|
|
return true;
|
|
|
}
|
|
|
|
|
|
function has(value) {
|
|
|
var index = hash(value) & mask,
|
|
|
match = store[index],
|
|
|
collisions = 0;
|
|
|
while (match != empty) {
|
|
|
if (equal(match, value)) return true;
|
|
|
if (++collisions >= size) break;
|
|
|
match = store[index = (index + 1) & mask];
|
|
|
}
|
|
|
return false;
|
|
|
}
|
|
|
|
|
|
function values() {
|
|
|
var values = [];
|
|
|
for (var i = 0, n = store.length; i < n; ++i) {
|
|
|
var match = store[i];
|
|
|
if (match != empty) values.push(match);
|
|
|
}
|
|
|
return values;
|
|
|
}
|
|
|
|
|
|
return {
|
|
|
add: add,
|
|
|
has: has,
|
|
|
values: values
|
|
|
};
|
|
|
};
|
|
|
|
|
|
var hashmap = function(size, hash, equal, keyType, keyEmpty, valueType) {
|
|
|
if (arguments.length === 3) {
|
|
|
keyType = valueType = Array;
|
|
|
keyEmpty = null;
|
|
|
}
|
|
|
|
|
|
var keystore = new keyType(size = 1 << Math.max(4, Math.ceil(Math.log(size) / Math.LN2))),
|
|
|
valstore = new valueType(size),
|
|
|
mask = size - 1;
|
|
|
|
|
|
for (var i = 0; i < size; ++i) {
|
|
|
keystore[i] = keyEmpty;
|
|
|
}
|
|
|
|
|
|
function set(key, value) {
|
|
|
var index = hash(key) & mask,
|
|
|
matchKey = keystore[index],
|
|
|
collisions = 0;
|
|
|
while (matchKey != keyEmpty) {
|
|
|
if (equal(matchKey, key)) return valstore[index] = value;
|
|
|
if (++collisions >= size) throw new Error("full hashmap");
|
|
|
matchKey = keystore[index = (index + 1) & mask];
|
|
|
}
|
|
|
keystore[index] = key;
|
|
|
valstore[index] = value;
|
|
|
return value;
|
|
|
}
|
|
|
|
|
|
function maybeSet(key, value) {
|
|
|
var index = hash(key) & mask,
|
|
|
matchKey = keystore[index],
|
|
|
collisions = 0;
|
|
|
while (matchKey != keyEmpty) {
|
|
|
if (equal(matchKey, key)) return valstore[index];
|
|
|
if (++collisions >= size) throw new Error("full hashmap");
|
|
|
matchKey = keystore[index = (index + 1) & mask];
|
|
|
}
|
|
|
keystore[index] = key;
|
|
|
valstore[index] = value;
|
|
|
return value;
|
|
|
}
|
|
|
|
|
|
function get(key, missingValue) {
|
|
|
var index = hash(key) & mask,
|
|
|
matchKey = keystore[index],
|
|
|
collisions = 0;
|
|
|
while (matchKey != keyEmpty) {
|
|
|
if (equal(matchKey, key)) return valstore[index];
|
|
|
if (++collisions >= size) break;
|
|
|
matchKey = keystore[index = (index + 1) & mask];
|
|
|
}
|
|
|
return missingValue;
|
|
|
}
|
|
|
|
|
|
function keys() {
|
|
|
var keys = [];
|
|
|
for (var i = 0, n = keystore.length; i < n; ++i) {
|
|
|
var matchKey = keystore[i];
|
|
|
if (matchKey != keyEmpty) keys.push(matchKey);
|
|
|
}
|
|
|
return keys;
|
|
|
}
|
|
|
|
|
|
return {
|
|
|
set: set,
|
|
|
maybeSet: maybeSet, // set if unset
|
|
|
get: get,
|
|
|
keys: keys
|
|
|
};
|
|
|
};
|
|
|
|
|
|
var equalPoint = function(pointA, pointB) {
|
|
|
return pointA[0] === pointB[0] && pointA[1] === pointB[1];
|
|
|
};
|
|
|
|
|
|
// TODO if quantized, use simpler Int32 hashing?
|
|
|
|
|
|
var buffer = new ArrayBuffer(16);
|
|
|
var uints = new Uint32Array(buffer);
|
|
|
|
|
|
var hashPoint = function(point) {
|
|
|
var hash = uints[0] ^ uints[1];
|
|
|
hash = hash << 5 ^ hash >> 7 ^ uints[2] ^ uints[3];
|
|
|
return hash & 0x7fffffff;
|
|
|
};
|
|
|
|
|
|
// Given an extracted (pre-)topology, identifies all of the junctions. These are
|
|
|
// the points at which arcs (lines or rings) will need to be cut so that each
|
|
|
// arc is represented uniquely.
|
|
|
//
|
|
|
// A junction is a point where at least one arc deviates from another arc going
|
|
|
// through the same point. For example, consider the point B. If there is a arc
|
|
|
// through ABC and another arc through CBA, then B is not a junction because in
|
|
|
// both cases the adjacent point pairs are {A,C}. However, if there is an
|
|
|
// additional arc ABD, then {A,D} != {A,C}, and thus B becomes a junction.
|
|
|
//
|
|
|
// For a closed ring ABCA, the first point A’s adjacent points are the second
|
|
|
// and last point {B,C}. For a line, the first and last point are always
|
|
|
// considered junctions, even if the line is closed; this ensures that a closed
|
|
|
// line is never rotated.
|
|
|
var join = function(topology) {
|
|
|
var coordinates = topology.coordinates,
|
|
|
lines = topology.lines,
|
|
|
rings = topology.rings,
|
|
|
indexes = index(),
|
|
|
visitedByIndex = new Int32Array(coordinates.length),
|
|
|
leftByIndex = new Int32Array(coordinates.length),
|
|
|
rightByIndex = new Int32Array(coordinates.length),
|
|
|
junctionByIndex = new Int8Array(coordinates.length),
|
|
|
junctionCount = 0, // upper bound on number of junctions
|
|
|
i, n,
|
|
|
previousIndex,
|
|
|
currentIndex,
|
|
|
nextIndex;
|
|
|
|
|
|
for (i = 0, n = coordinates.length; i < n; ++i) {
|
|
|
visitedByIndex[i] = leftByIndex[i] = rightByIndex[i] = -1;
|
|
|
}
|
|
|
|
|
|
for (i = 0, n = lines.length; i < n; ++i) {
|
|
|
var line = lines[i],
|
|
|
lineStart = line[0],
|
|
|
lineEnd = line[1];
|
|
|
currentIndex = indexes[lineStart];
|
|
|
nextIndex = indexes[++lineStart];
|
|
|
++junctionCount, junctionByIndex[currentIndex] = 1; // start
|
|
|
while (++lineStart <= lineEnd) {
|
|
|
sequence(i, previousIndex = currentIndex, currentIndex = nextIndex, nextIndex = indexes[lineStart]);
|
|
|
}
|
|
|
++junctionCount, junctionByIndex[nextIndex] = 1; // end
|
|
|
}
|
|
|
|
|
|
for (i = 0, n = coordinates.length; i < n; ++i) {
|
|
|
visitedByIndex[i] = -1;
|
|
|
}
|
|
|
|
|
|
for (i = 0, n = rings.length; i < n; ++i) {
|
|
|
var ring = rings[i],
|
|
|
ringStart = ring[0] + 1,
|
|
|
ringEnd = ring[1];
|
|
|
previousIndex = indexes[ringEnd - 1];
|
|
|
currentIndex = indexes[ringStart - 1];
|
|
|
nextIndex = indexes[ringStart];
|
|
|
sequence(i, previousIndex, currentIndex, nextIndex);
|
|
|
while (++ringStart <= ringEnd) {
|
|
|
sequence(i, previousIndex = currentIndex, currentIndex = nextIndex, nextIndex = indexes[ringStart]);
|
|
|
}
|
|
|
}
|
|
|
|
|
|
function sequence(i, previousIndex, currentIndex, nextIndex) {
|
|
|
if (visitedByIndex[currentIndex] === i) return; // ignore self-intersection
|
|
|
visitedByIndex[currentIndex] = i;
|
|
|
var leftIndex = leftByIndex[currentIndex];
|
|
|
if (leftIndex >= 0) {
|
|
|
var rightIndex = rightByIndex[currentIndex];
|
|
|
if ((leftIndex !== previousIndex || rightIndex !== nextIndex)
|
|
|
&& (leftIndex !== nextIndex || rightIndex !== previousIndex)) {
|
|
|
++junctionCount, junctionByIndex[currentIndex] = 1;
|
|
|
}
|
|
|
} else {
|
|
|
leftByIndex[currentIndex] = previousIndex;
|
|
|
rightByIndex[currentIndex] = nextIndex;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
function index() {
|
|
|
var indexByPoint = hashmap(coordinates.length * 1.4, hashIndex, equalIndex, Int32Array, -1, Int32Array),
|
|
|
indexes = new Int32Array(coordinates.length);
|
|
|
|
|
|
for (var i = 0, n = coordinates.length; i < n; ++i) {
|
|
|
indexes[i] = indexByPoint.maybeSet(i, i);
|
|
|
}
|
|
|
|
|
|
return indexes;
|
|
|
}
|
|
|
|
|
|
function hashIndex(i) {
|
|
|
return hashPoint(coordinates[i]);
|
|
|
}
|
|
|
|
|
|
function equalIndex(i, j) {
|
|
|
return equalPoint(coordinates[i], coordinates[j]);
|
|
|
}
|
|
|
|
|
|
visitedByIndex = leftByIndex = rightByIndex = null;
|
|
|
|
|
|
var junctionByPoint = hashset(junctionCount * 1.4, hashPoint, equalPoint), j;
|
|
|
|
|
|
// Convert back to a standard hashset by point for caller convenience.
|
|
|
for (i = 0, n = coordinates.length; i < n; ++i) {
|
|
|
if (junctionByIndex[j = indexes[i]]) {
|
|
|
junctionByPoint.add(coordinates[j]);
|
|
|
}
|
|
|
}
|
|
|
|
|
|
return junctionByPoint;
|
|
|
};
|
|
|
|
|
|
// Given an extracted (pre-)topology, cuts (or rotates) arcs so that all shared
|
|
|
// point sequences are identified. The topology can then be subsequently deduped
|
|
|
// to remove exact duplicate arcs.
|
|
|
var cut = function(topology) {
|
|
|
var junctions = join(topology),
|
|
|
coordinates = topology.coordinates,
|
|
|
lines = topology.lines,
|
|
|
rings = topology.rings,
|
|
|
next,
|
|
|
i, n;
|
|
|
|
|
|
for (i = 0, n = lines.length; i < n; ++i) {
|
|
|
var line = lines[i],
|
|
|
lineMid = line[0],
|
|
|
lineEnd = line[1];
|
|
|
while (++lineMid < lineEnd) {
|
|
|
if (junctions.has(coordinates[lineMid])) {
|
|
|
next = {0: lineMid, 1: line[1]};
|
|
|
line[1] = lineMid;
|
|
|
line = line.next = next;
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
|
|
|
for (i = 0, n = rings.length; i < n; ++i) {
|
|
|
var ring = rings[i],
|
|
|
ringStart = ring[0],
|
|
|
ringMid = ringStart,
|
|
|
ringEnd = ring[1],
|
|
|
ringFixed = junctions.has(coordinates[ringStart]);
|
|
|
while (++ringMid < ringEnd) {
|
|
|
if (junctions.has(coordinates[ringMid])) {
|
|
|
if (ringFixed) {
|
|
|
next = {0: ringMid, 1: ring[1]};
|
|
|
ring[1] = ringMid;
|
|
|
ring = ring.next = next;
|
|
|
} else { // For the first junction, we can rotate rather than cut.
|
|
|
rotateArray(coordinates, ringStart, ringEnd, ringEnd - ringMid);
|
|
|
coordinates[ringEnd] = coordinates[ringStart];
|
|
|
ringFixed = true;
|
|
|
ringMid = ringStart; // restart; we may have skipped junctions
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
|
|
|
return topology;
|
|
|
};
|
|
|
|
|
|
function rotateArray(array, start, end, offset) {
|
|
|
reverse$1(array, start, end);
|
|
|
reverse$1(array, start, start + offset);
|
|
|
reverse$1(array, start + offset, end);
|
|
|
}
|
|
|
|
|
|
function reverse$1(array, start, end) {
|
|
|
for (var mid = start + ((end-- - start) >> 1), t; start < mid; ++start, --end) {
|
|
|
t = array[start], array[start] = array[end], array[end] = t;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
// Given a cut topology, combines duplicate arcs.
|
|
|
var dedup = function(topology) {
|
|
|
var coordinates = topology.coordinates,
|
|
|
lines = topology.lines, line,
|
|
|
rings = topology.rings, ring,
|
|
|
arcCount = lines.length + rings.length,
|
|
|
i, n;
|
|
|
|
|
|
delete topology.lines;
|
|
|
delete topology.rings;
|
|
|
|
|
|
// Count the number of (non-unique) arcs to initialize the hashmap safely.
|
|
|
for (i = 0, n = lines.length; i < n; ++i) {
|
|
|
line = lines[i]; while (line = line.next) ++arcCount;
|
|
|
}
|
|
|
for (i = 0, n = rings.length; i < n; ++i) {
|
|
|
ring = rings[i]; while (ring = ring.next) ++arcCount;
|
|
|
}
|
|
|
|
|
|
var arcsByEnd = hashmap(arcCount * 2 * 1.4, hashPoint, equalPoint),
|
|
|
arcs = topology.arcs = [];
|
|
|
|
|
|
for (i = 0, n = lines.length; i < n; ++i) {
|
|
|
line = lines[i];
|
|
|
do {
|
|
|
dedupLine(line);
|
|
|
} while (line = line.next);
|
|
|
}
|
|
|
|
|
|
for (i = 0, n = rings.length; i < n; ++i) {
|
|
|
ring = rings[i];
|
|
|
if (ring.next) { // arc is no longer closed
|
|
|
do {
|
|
|
dedupLine(ring);
|
|
|
} while (ring = ring.next);
|
|
|
} else {
|
|
|
dedupRing(ring);
|
|
|
}
|
|
|
}
|
|
|
|
|
|
function dedupLine(arc) {
|
|
|
var startPoint,
|
|
|
endPoint,
|
|
|
startArcs, startArc,
|
|
|
endArcs, endArc,
|
|
|
i, n;
|
|
|
|
|
|
// Does this arc match an existing arc in order?
|
|
|
if (startArcs = arcsByEnd.get(startPoint = coordinates[arc[0]])) {
|
|
|
for (i = 0, n = startArcs.length; i < n; ++i) {
|
|
|
startArc = startArcs[i];
|
|
|
if (equalLine(startArc, arc)) {
|
|
|
arc[0] = startArc[0];
|
|
|
arc[1] = startArc[1];
|
|
|
return;
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
|
|
|
// Does this arc match an existing arc in reverse order?
|
|
|
if (endArcs = arcsByEnd.get(endPoint = coordinates[arc[1]])) {
|
|
|
for (i = 0, n = endArcs.length; i < n; ++i) {
|
|
|
endArc = endArcs[i];
|
|
|
if (reverseEqualLine(endArc, arc)) {
|
|
|
arc[1] = endArc[0];
|
|
|
arc[0] = endArc[1];
|
|
|
return;
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
|
|
|
if (startArcs) startArcs.push(arc); else arcsByEnd.set(startPoint, [arc]);
|
|
|
if (endArcs) endArcs.push(arc); else arcsByEnd.set(endPoint, [arc]);
|
|
|
arcs.push(arc);
|
|
|
}
|
|
|
|
|
|
function dedupRing(arc) {
|
|
|
var endPoint,
|
|
|
endArcs,
|
|
|
endArc,
|
|
|
i, n;
|
|
|
|
|
|
// Does this arc match an existing line in order, or reverse order?
|
|
|
// Rings are closed, so their start point and end point is the same.
|
|
|
if (endArcs = arcsByEnd.get(endPoint = coordinates[arc[0]])) {
|
|
|
for (i = 0, n = endArcs.length; i < n; ++i) {
|
|
|
endArc = endArcs[i];
|
|
|
if (equalRing(endArc, arc)) {
|
|
|
arc[0] = endArc[0];
|
|
|
arc[1] = endArc[1];
|
|
|
return;
|
|
|
}
|
|
|
if (reverseEqualRing(endArc, arc)) {
|
|
|
arc[0] = endArc[1];
|
|
|
arc[1] = endArc[0];
|
|
|
return;
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
|
|
|
// Otherwise, does this arc match an existing ring in order, or reverse order?
|
|
|
if (endArcs = arcsByEnd.get(endPoint = coordinates[arc[0] + findMinimumOffset(arc)])) {
|
|
|
for (i = 0, n = endArcs.length; i < n; ++i) {
|
|
|
endArc = endArcs[i];
|
|
|
if (equalRing(endArc, arc)) {
|
|
|
arc[0] = endArc[0];
|
|
|
arc[1] = endArc[1];
|
|
|
return;
|
|
|
}
|
|
|
if (reverseEqualRing(endArc, arc)) {
|
|
|
arc[0] = endArc[1];
|
|
|
arc[1] = endArc[0];
|
|
|
return;
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
|
|
|
if (endArcs) endArcs.push(arc); else arcsByEnd.set(endPoint, [arc]);
|
|
|
arcs.push(arc);
|
|
|
}
|
|
|
|
|
|
function equalLine(arcA, arcB) {
|
|
|
var ia = arcA[0], ib = arcB[0],
|
|
|
ja = arcA[1], jb = arcB[1];
|
|
|
if (ia - ja !== ib - jb) return false;
|
|
|
for (; ia <= ja; ++ia, ++ib) if (!equalPoint(coordinates[ia], coordinates[ib])) return false;
|
|
|
return true;
|
|
|
}
|
|
|
|
|
|
function reverseEqualLine(arcA, arcB) {
|
|
|
var ia = arcA[0], ib = arcB[0],
|
|
|
ja = arcA[1], jb = arcB[1];
|
|
|
if (ia - ja !== ib - jb) return false;
|
|
|
for (; ia <= ja; ++ia, --jb) if (!equalPoint(coordinates[ia], coordinates[jb])) return false;
|
|
|
return true;
|
|
|
}
|
|
|
|
|
|
function equalRing(arcA, arcB) {
|
|
|
var ia = arcA[0], ib = arcB[0],
|
|
|
ja = arcA[1], jb = arcB[1],
|
|
|
n = ja - ia;
|
|
|
if (n !== jb - ib) return false;
|
|
|
var ka = findMinimumOffset(arcA),
|
|
|
kb = findMinimumOffset(arcB);
|
|
|
for (var i = 0; i < n; ++i) {
|
|
|
if (!equalPoint(coordinates[ia + (i + ka) % n], coordinates[ib + (i + kb) % n])) return false;
|
|
|
}
|
|
|
return true;
|
|
|
}
|
|
|
|
|
|
function reverseEqualRing(arcA, arcB) {
|
|
|
var ia = arcA[0], ib = arcB[0],
|
|
|
ja = arcA[1], jb = arcB[1],
|
|
|
n = ja - ia;
|
|
|
if (n !== jb - ib) return false;
|
|
|
var ka = findMinimumOffset(arcA),
|
|
|
kb = n - findMinimumOffset(arcB);
|
|
|
for (var i = 0; i < n; ++i) {
|
|
|
if (!equalPoint(coordinates[ia + (i + ka) % n], coordinates[jb - (i + kb) % n])) return false;
|
|
|
}
|
|
|
return true;
|
|
|
}
|
|
|
|
|
|
// Rings are rotated to a consistent, but arbitrary, start point.
|
|
|
// This is necessary to detect when a ring and a rotated copy are dupes.
|
|
|
function findMinimumOffset(arc) {
|
|
|
var start = arc[0],
|
|
|
end = arc[1],
|
|
|
mid = start,
|
|
|
minimum = mid,
|
|
|
minimumPoint = coordinates[mid];
|
|
|
while (++mid < end) {
|
|
|
var point = coordinates[mid];
|
|
|
if (point[0] < minimumPoint[0] || point[0] === minimumPoint[0] && point[1] < minimumPoint[1]) {
|
|
|
minimum = mid;
|
|
|
minimumPoint = point;
|
|
|
}
|
|
|
}
|
|
|
return minimum - start;
|
|
|
}
|
|
|
|
|
|
return topology;
|
|
|
};
|
|
|
|
|
|
// Given an array of arcs in absolute (but already quantized!) coordinates,
|
|
|
// converts to fixed-point delta encoding.
|
|
|
// This is a destructive operation that modifies the given arcs!
|
|
|
var delta = function(arcs) {
|
|
|
var i = -1,
|
|
|
n = arcs.length;
|
|
|
|
|
|
while (++i < n) {
|
|
|
var arc = arcs[i],
|
|
|
j = 0,
|
|
|
k = 1,
|
|
|
m = arc.length,
|
|
|
point = arc[0],
|
|
|
x0 = point[0],
|
|
|
y0 = point[1],
|
|
|
x1,
|
|
|
y1;
|
|
|
|
|
|
while (++j < m) {
|
|
|
point = arc[j], x1 = point[0], y1 = point[1];
|
|
|
if (x1 !== x0 || y1 !== y0) arc[k++] = [x1 - x0, y1 - y0], x0 = x1, y0 = y1;
|
|
|
}
|
|
|
|
|
|
if (k === 1) arc[k++] = [0, 0]; // Each arc must be an array of two or more positions.
|
|
|
|
|
|
arc.length = k;
|
|
|
}
|
|
|
|
|
|
return arcs;
|
|
|
};
|
|
|
|
|
|
// Extracts the lines and rings from the specified hash of geometry objects.
|
|
|
//
|
|
|
// Returns an object with three properties:
|
|
|
//
|
|
|
// * coordinates - shared buffer of [x, y] coordinates
|
|
|
// * lines - lines extracted from the hash, of the form [start, end]
|
|
|
// * rings - rings extracted from the hash, of the form [start, end]
|
|
|
//
|
|
|
// For each ring or line, start and end represent inclusive indexes into the
|
|
|
// coordinates buffer. For rings (and closed lines), coordinates[start] equals
|
|
|
// coordinates[end].
|
|
|
//
|
|
|
// For each line or polygon geometry in the input hash, including nested
|
|
|
// geometries as in geometry collections, the `coordinates` array is replaced
|
|
|
// with an equivalent `arcs` array that, for each line (for line string
|
|
|
// geometries) or ring (for polygon geometries), points to one of the above
|
|
|
// lines or rings.
|
|
|
var extract = function(objects) {
|
|
|
var index = -1,
|
|
|
lines = [],
|
|
|
rings = [],
|
|
|
coordinates = [];
|
|
|
|
|
|
function extractGeometry(geometry) {
|
|
|
if (geometry && extractGeometryType.hasOwnProperty(geometry.type)) extractGeometryType[geometry.type](geometry);
|
|
|
}
|
|
|
|
|
|
var extractGeometryType = {
|
|
|
GeometryCollection: function(o) { o.geometries.forEach(extractGeometry); },
|
|
|
LineString: function(o) { o.arcs = extractLine(o.arcs); },
|
|
|
MultiLineString: function(o) { o.arcs = o.arcs.map(extractLine); },
|
|
|
Polygon: function(o) { o.arcs = o.arcs.map(extractRing); },
|
|
|
MultiPolygon: function(o) { o.arcs = o.arcs.map(extractMultiRing); }
|
|
|
};
|
|
|
|
|
|
function extractLine(line) {
|
|
|
for (var i = 0, n = line.length; i < n; ++i) coordinates[++index] = line[i];
|
|
|
var arc = {0: index - n + 1, 1: index};
|
|
|
lines.push(arc);
|
|
|
return arc;
|
|
|
}
|
|
|
|
|
|
function extractRing(ring) {
|
|
|
for (var i = 0, n = ring.length; i < n; ++i) coordinates[++index] = ring[i];
|
|
|
var arc = {0: index - n + 1, 1: index};
|
|
|
rings.push(arc);
|
|
|
return arc;
|
|
|
}
|
|
|
|
|
|
function extractMultiRing(rings) {
|
|
|
return rings.map(extractRing);
|
|
|
}
|
|
|
|
|
|
for (var key in objects) {
|
|
|
extractGeometry(objects[key]);
|
|
|
}
|
|
|
|
|
|
return {
|
|
|
type: "Topology",
|
|
|
coordinates: coordinates,
|
|
|
lines: lines,
|
|
|
rings: rings,
|
|
|
objects: objects
|
|
|
};
|
|
|
};
|
|
|
|
|
|
// Given a hash of GeoJSON objects, returns a hash of GeoJSON geometry objects.
|
|
|
// Any null input geometry objects are represented as {type: null} in the output.
|
|
|
// Any feature.{id,properties,bbox} are transferred to the output geometry object.
|
|
|
// Each output geometry object is a shallow copy of the input (e.g., properties, coordinates)!
|
|
|
var geometry = function(inputs) {
|
|
|
var outputs = {}, key;
|
|
|
for (key in inputs) outputs[key] = geomifyObject(inputs[key]);
|
|
|
return outputs;
|
|
|
};
|
|
|
|
|
|
function geomifyObject(input) {
|
|
|
return input == null ? {type: null}
|
|
|
: (input.type === "FeatureCollection" ? geomifyFeatureCollection
|
|
|
: input.type === "Feature" ? geomifyFeature
|
|
|
: geomifyGeometry)(input);
|
|
|
}
|
|
|
|
|
|
function geomifyFeatureCollection(input) {
|
|
|
var output = {type: "GeometryCollection", geometries: input.features.map(geomifyFeature)};
|
|
|
if (input.bbox != null) output.bbox = input.bbox;
|
|
|
return output;
|
|
|
}
|
|
|
|
|
|
function geomifyFeature(input) {
|
|
|
var output = geomifyGeometry(input.geometry), key; // eslint-disable-line no-unused-vars
|
|
|
if (input.id != null) output.id = input.id;
|
|
|
if (input.bbox != null) output.bbox = input.bbox;
|
|
|
for (key in input.properties) { output.properties = input.properties; break; }
|
|
|
return output;
|
|
|
}
|
|
|
|
|
|
function geomifyGeometry(input) {
|
|
|
if (input == null) return {type: null};
|
|
|
var output = input.type === "GeometryCollection" ? {type: "GeometryCollection", geometries: input.geometries.map(geomifyGeometry)}
|
|
|
: input.type === "Point" || input.type === "MultiPoint" ? {type: input.type, coordinates: input.coordinates}
|
|
|
: {type: input.type, arcs: input.coordinates}; // TODO Check for unknown types?
|
|
|
if (input.bbox != null) output.bbox = input.bbox;
|
|
|
return output;
|
|
|
}
|
|
|
|
|
|
var prequantize = function(objects, bbox, n) {
|
|
|
var x0 = bbox[0],
|
|
|
y0 = bbox[1],
|
|
|
x1 = bbox[2],
|
|
|
y1 = bbox[3],
|
|
|
kx = x1 - x0 ? (n - 1) / (x1 - x0) : 1,
|
|
|
ky = y1 - y0 ? (n - 1) / (y1 - y0) : 1;
|
|
|
|
|
|
function quantizePoint(input) {
|
|
|
return [Math.round((input[0] - x0) * kx), Math.round((input[1] - y0) * ky)];
|
|
|
}
|
|
|
|
|
|
function quantizePoints(input, m) {
|
|
|
var i = -1,
|
|
|
j = 0,
|
|
|
n = input.length,
|
|
|
output = new Array(n), // pessimistic
|
|
|
pi,
|
|
|
px,
|
|
|
py,
|
|
|
x,
|
|
|
y;
|
|
|
|
|
|
while (++i < n) {
|
|
|
pi = input[i];
|
|
|
x = Math.round((pi[0] - x0) * kx);
|
|
|
y = Math.round((pi[1] - y0) * ky);
|
|
|
if (x !== px || y !== py) output[j++] = [px = x, py = y]; // non-coincident points
|
|
|
}
|
|
|
|
|
|
output.length = j;
|
|
|
while (j < m) j = output.push([output[0][0], output[0][1]]);
|
|
|
return output;
|
|
|
}
|
|
|
|
|
|
function quantizeLine(input) {
|
|
|
return quantizePoints(input, 2);
|
|
|
}
|
|
|
|
|
|
function quantizeRing(input) {
|
|
|
return quantizePoints(input, 4);
|
|
|
}
|
|
|
|
|
|
function quantizePolygon(input) {
|
|
|
return input.map(quantizeRing);
|
|
|
}
|
|
|
|
|
|
function quantizeGeometry(o) {
|
|
|
if (o != null && quantizeGeometryType.hasOwnProperty(o.type)) quantizeGeometryType[o.type](o);
|
|
|
}
|
|
|
|
|
|
var quantizeGeometryType = {
|
|
|
GeometryCollection: function(o) { o.geometries.forEach(quantizeGeometry); },
|
|
|
Point: function(o) { o.coordinates = quantizePoint(o.coordinates); },
|
|
|
MultiPoint: function(o) { o.coordinates = o.coordinates.map(quantizePoint); },
|
|
|
LineString: function(o) { o.arcs = quantizeLine(o.arcs); },
|
|
|
MultiLineString: function(o) { o.arcs = o.arcs.map(quantizeLine); },
|
|
|
Polygon: function(o) { o.arcs = quantizePolygon(o.arcs); },
|
|
|
MultiPolygon: function(o) { o.arcs = o.arcs.map(quantizePolygon); }
|
|
|
};
|
|
|
|
|
|
for (var key in objects) {
|
|
|
quantizeGeometry(objects[key]);
|
|
|
}
|
|
|
|
|
|
return {
|
|
|
scale: [1 / kx, 1 / ky],
|
|
|
translate: [x0, y0]
|
|
|
};
|
|
|
};
|
|
|
|
|
|
// Constructs the TopoJSON Topology for the specified hash of features.
|
|
|
// Each object in the specified hash must be a GeoJSON object,
|
|
|
// meaning FeatureCollection, a Feature or a geometry object.
|
|
|
var topology = function(objects, quantization) {
|
|
|
var bbox = bounds(objects = geometry(objects)),
|
|
|
transform = quantization > 0 && bbox && prequantize(objects, bbox, quantization),
|
|
|
topology = dedup(cut(extract(objects))),
|
|
|
coordinates = topology.coordinates,
|
|
|
indexByArc = hashmap(topology.arcs.length * 1.4, hashArc, equalArc);
|
|
|
|
|
|
objects = topology.objects; // for garbage collection
|
|
|
topology.bbox = bbox;
|
|
|
topology.arcs = topology.arcs.map(function(arc, i) {
|
|
|
indexByArc.set(arc, i);
|
|
|
return coordinates.slice(arc[0], arc[1] + 1);
|
|
|
});
|
|
|
|
|
|
delete topology.coordinates;
|
|
|
coordinates = null;
|
|
|
|
|
|
function indexGeometry(geometry$$1) {
|
|
|
if (geometry$$1 && indexGeometryType.hasOwnProperty(geometry$$1.type)) indexGeometryType[geometry$$1.type](geometry$$1);
|
|
|
}
|
|
|
|
|
|
var indexGeometryType = {
|
|
|
GeometryCollection: function(o) { o.geometries.forEach(indexGeometry); },
|
|
|
LineString: function(o) { o.arcs = indexArcs(o.arcs); },
|
|
|
MultiLineString: function(o) { o.arcs = o.arcs.map(indexArcs); },
|
|
|
Polygon: function(o) { o.arcs = o.arcs.map(indexArcs); },
|
|
|
MultiPolygon: function(o) { o.arcs = o.arcs.map(indexMultiArcs); }
|
|
|
};
|
|
|
|
|
|
function indexArcs(arc) {
|
|
|
var indexes = [];
|
|
|
do {
|
|
|
var index = indexByArc.get(arc);
|
|
|
indexes.push(arc[0] < arc[1] ? index : ~index);
|
|
|
} while (arc = arc.next);
|
|
|
return indexes;
|
|
|
}
|
|
|
|
|
|
function indexMultiArcs(arcs) {
|
|
|
return arcs.map(indexArcs);
|
|
|
}
|
|
|
|
|
|
for (var key in objects) {
|
|
|
indexGeometry(objects[key]);
|
|
|
}
|
|
|
|
|
|
if (transform) {
|
|
|
topology.transform = transform;
|
|
|
topology.arcs = delta(topology.arcs);
|
|
|
}
|
|
|
|
|
|
return topology;
|
|
|
};
|
|
|
|
|
|
function hashArc(arc) {
|
|
|
var i = arc[0], j = arc[1], t;
|
|
|
if (j < i) t = i, i = j, j = t;
|
|
|
return i + 31 * j;
|
|
|
}
|
|
|
|
|
|
function equalArc(arcA, arcB) {
|
|
|
var ia = arcA[0], ja = arcA[1],
|
|
|
ib = arcB[0], jb = arcB[1], t;
|
|
|
if (ja < ia) t = ia, ia = ja, ja = t;
|
|
|
if (jb < ib) t = ib, ib = jb, jb = t;
|
|
|
return ia === ib && ja === jb;
|
|
|
}
|
|
|
|
|
|
var prune = function(topology) {
|
|
|
var oldObjects = topology.objects,
|
|
|
newObjects = {},
|
|
|
oldArcs = topology.arcs,
|
|
|
oldArcsLength = oldArcs.length,
|
|
|
oldIndex = -1,
|
|
|
newIndexByOldIndex = new Array(oldArcsLength),
|
|
|
newArcsLength = 0,
|
|
|
newArcs,
|
|
|
newIndex = -1,
|
|
|
key;
|
|
|
|
|
|
function scanGeometry(input) {
|
|
|
switch (input.type) {
|
|
|
case "GeometryCollection": input.geometries.forEach(scanGeometry); break;
|
|
|
case "LineString": scanArcs(input.arcs); break;
|
|
|
case "MultiLineString": input.arcs.forEach(scanArcs); break;
|
|
|
case "Polygon": input.arcs.forEach(scanArcs); break;
|
|
|
case "MultiPolygon": input.arcs.forEach(scanMultiArcs); break;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
function scanArc(index) {
|
|
|
if (index < 0) index = ~index;
|
|
|
if (!newIndexByOldIndex[index]) newIndexByOldIndex[index] = 1, ++newArcsLength;
|
|
|
}
|
|
|
|
|
|
function scanArcs(arcs) {
|
|
|
arcs.forEach(scanArc);
|
|
|
}
|
|
|
|
|
|
function scanMultiArcs(arcs) {
|
|
|
arcs.forEach(scanArcs);
|
|
|
}
|
|
|
|
|
|
function reindexGeometry(input) {
|
|
|
var output;
|
|
|
switch (input.type) {
|
|
|
case "GeometryCollection": output = {type: "GeometryCollection", geometries: input.geometries.map(reindexGeometry)}; break;
|
|
|
case "LineString": output = {type: "LineString", arcs: reindexArcs(input.arcs)}; break;
|
|
|
case "MultiLineString": output = {type: "MultiLineString", arcs: input.arcs.map(reindexArcs)}; break;
|
|
|
case "Polygon": output = {type: "Polygon", arcs: input.arcs.map(reindexArcs)}; break;
|
|
|
case "MultiPolygon": output = {type: "MultiPolygon", arcs: input.arcs.map(reindexMultiArcs)}; break;
|
|
|
default: return input;
|
|
|
}
|
|
|
if (input.id != null) output.id = input.id;
|
|
|
if (input.bbox != null) output.bbox = input.bbox;
|
|
|
if (input.properties != null) output.properties = input.properties;
|
|
|
return output;
|
|
|
}
|
|
|
|
|
|
function reindexArc(oldIndex) {
|
|
|
return oldIndex < 0 ? ~newIndexByOldIndex[~oldIndex] : newIndexByOldIndex[oldIndex];
|
|
|
}
|
|
|
|
|
|
function reindexArcs(arcs) {
|
|
|
return arcs.map(reindexArc);
|
|
|
}
|
|
|
|
|
|
function reindexMultiArcs(arcs) {
|
|
|
return arcs.map(reindexArcs);
|
|
|
}
|
|
|
|
|
|
for (key in oldObjects) {
|
|
|
scanGeometry(oldObjects[key]);
|
|
|
}
|
|
|
|
|
|
newArcs = new Array(newArcsLength);
|
|
|
|
|
|
while (++oldIndex < oldArcsLength) {
|
|
|
if (newIndexByOldIndex[oldIndex]) {
|
|
|
newIndexByOldIndex[oldIndex] = ++newIndex;
|
|
|
newArcs[newIndex] = oldArcs[oldIndex];
|
|
|
}
|
|
|
}
|
|
|
|
|
|
for (key in oldObjects) {
|
|
|
newObjects[key] = reindexGeometry(oldObjects[key]);
|
|
|
}
|
|
|
|
|
|
return {
|
|
|
type: "Topology",
|
|
|
bbox: topology.bbox,
|
|
|
transform: topology.transform,
|
|
|
objects: newObjects,
|
|
|
arcs: newArcs
|
|
|
};
|
|
|
};
|
|
|
|
|
|
var filter = function(topology, filter) {
|
|
|
var oldObjects = topology.objects,
|
|
|
newObjects = {},
|
|
|
key;
|
|
|
|
|
|
if (filter == null) filter = filterTrue;
|
|
|
|
|
|
function filterGeometry(input) {
|
|
|
var output, arcs;
|
|
|
switch (input.type) {
|
|
|
case "Polygon": {
|
|
|
arcs = filterRings(input.arcs);
|
|
|
output = arcs ? {type: "Polygon", arcs: arcs} : {type: null};
|
|
|
break;
|
|
|
}
|
|
|
case "MultiPolygon": {
|
|
|
arcs = input.arcs.map(filterRings).filter(filterIdentity);
|
|
|
output = arcs.length ? {type: "MultiPolygon", arcs: arcs} : {type: null};
|
|
|
break;
|
|
|
}
|
|
|
case "GeometryCollection": {
|
|
|
arcs = input.geometries.map(filterGeometry).filter(filterNotNull);
|
|
|
output = arcs.length ? {type: "GeometryCollection", geometries: arcs} : {type: null};
|
|
|
break;
|
|
|
}
|
|
|
default: return input;
|
|
|
}
|
|
|
if (input.id != null) output.id = input.id;
|
|
|
if (input.bbox != null) output.bbox = input.bbox;
|
|
|
if (input.properties != null) output.properties = input.properties;
|
|
|
return output;
|
|
|
}
|
|
|
|
|
|
function filterRings(arcs) {
|
|
|
return arcs.length && filterExteriorRing(arcs[0]) // if the exterior is small, ignore any holes
|
|
|
? [arcs[0]].concat(arcs.slice(1).filter(filterInteriorRing))
|
|
|
: null;
|
|
|
}
|
|
|
|
|
|
function filterExteriorRing(ring) {
|
|
|
return filter(ring, false);
|
|
|
}
|
|
|
|
|
|
function filterInteriorRing(ring) {
|
|
|
return filter(ring, true);
|
|
|
}
|
|
|
|
|
|
for (key in oldObjects) {
|
|
|
newObjects[key] = filterGeometry(oldObjects[key]);
|
|
|
}
|
|
|
|
|
|
return prune({
|
|
|
type: "Topology",
|
|
|
bbox: topology.bbox,
|
|
|
transform: topology.transform,
|
|
|
objects: newObjects,
|
|
|
arcs: topology.arcs
|
|
|
});
|
|
|
};
|
|
|
|
|
|
function filterTrue() {
|
|
|
return true;
|
|
|
}
|
|
|
|
|
|
function filterIdentity(x) {
|
|
|
return x;
|
|
|
}
|
|
|
|
|
|
function filterNotNull(geometry) {
|
|
|
return geometry.type != null;
|
|
|
}
|
|
|
|
|
|
var filterAttached = function(topology) {
|
|
|
var ownerByArc = new Array(topology.arcs.length), // arc index -> index of unique associated ring, or -1 if used by multiple rings
|
|
|
ownerIndex = 0,
|
|
|
key;
|
|
|
|
|
|
function testGeometry(o) {
|
|
|
switch (o.type) {
|
|
|
case "GeometryCollection": o.geometries.forEach(testGeometry); break;
|
|
|
case "Polygon": testArcs(o.arcs); break;
|
|
|
case "MultiPolygon": o.arcs.forEach(testArcs); break;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
function testArcs(arcs) {
|
|
|
for (var i = 0, n = arcs.length; i < n; ++i, ++ownerIndex) {
|
|
|
for (var ring = arcs[i], j = 0, m = ring.length; j < m; ++j) {
|
|
|
var arc = ring[j];
|
|
|
if (arc < 0) arc = ~arc;
|
|
|
var owner = ownerByArc[arc];
|
|
|
if (owner == null) ownerByArc[arc] = ownerIndex;
|
|
|
else if (owner !== ownerIndex) ownerByArc[arc] = -1;
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
|
|
|
for (key in topology.objects) {
|
|
|
testGeometry(topology.objects[key]);
|
|
|
}
|
|
|
|
|
|
return function(ring) {
|
|
|
for (var j = 0, m = ring.length, arc; j < m; ++j) {
|
|
|
if (ownerByArc[(arc = ring[j]) < 0 ? ~arc : arc] === -1) {
|
|
|
return true;
|
|
|
}
|
|
|
}
|
|
|
return false;
|
|
|
};
|
|
|
};
|
|
|
|
|
|
function planarTriangleArea(triangle) {
|
|
|
var a = triangle[0], b = triangle[1], c = triangle[2];
|
|
|
return Math.abs((a[0] - c[0]) * (b[1] - a[1]) - (a[0] - b[0]) * (c[1] - a[1])) / 2;
|
|
|
}
|
|
|
|
|
|
function planarRingArea$1(ring) {
|
|
|
var i = -1, n = ring.length, a, b = ring[n - 1], area = 0;
|
|
|
while (++i < n) a = b, b = ring[i], area += a[0] * b[1] - a[1] * b[0];
|
|
|
return Math.abs(area) / 2;
|
|
|
}
|
|
|
|
|
|
var filterWeight = function(topology, minWeight, weight) {
|
|
|
minWeight = minWeight == null ? Number.MIN_VALUE : +minWeight;
|
|
|
|
|
|
if (weight == null) weight = planarRingArea$1;
|
|
|
|
|
|
return function(ring, interior) {
|
|
|
return weight(feature(topology, {type: "Polygon", arcs: [ring]}).geometry.coordinates[0], interior) >= minWeight;
|
|
|
};
|
|
|
};
|
|
|
|
|
|
var filterAttachedWeight = function(topology, minWeight, weight) {
|
|
|
var a = filterAttached(topology),
|
|
|
w = filterWeight(topology, minWeight, weight);
|
|
|
return function(ring, interior) {
|
|
|
return a(ring, interior) || w(ring, interior);
|
|
|
};
|
|
|
};
|
|
|
|
|
|
function compare(a, b) {
|
|
|
return a[1][2] - b[1][2];
|
|
|
}
|
|
|
|
|
|
var newHeap = function() {
|
|
|
var heap = {},
|
|
|
array = [],
|
|
|
size = 0;
|
|
|
|
|
|
heap.push = function(object) {
|
|
|
up(array[object._ = size] = object, size++);
|
|
|
return size;
|
|
|
};
|
|
|
|
|
|
heap.pop = function() {
|
|
|
if (size <= 0) return;
|
|
|
var removed = array[0], object;
|
|
|
if (--size > 0) object = array[size], down(array[object._ = 0] = object, 0);
|
|
|
return removed;
|
|
|
};
|
|
|
|
|
|
heap.remove = function(removed) {
|
|
|
var i = removed._, object;
|
|
|
if (array[i] !== removed) return; // invalid request
|
|
|
if (i !== --size) object = array[size], (compare(object, removed) < 0 ? up : down)(array[object._ = i] = object, i);
|
|
|
return i;
|
|
|
};
|
|
|
|
|
|
function up(object, i) {
|
|
|
while (i > 0) {
|
|
|
var j = ((i + 1) >> 1) - 1,
|
|
|
parent = array[j];
|
|
|
if (compare(object, parent) >= 0) break;
|
|
|
array[parent._ = i] = parent;
|
|
|
array[object._ = i = j] = object;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
function down(object, i) {
|
|
|
while (true) {
|
|
|
var r = (i + 1) << 1,
|
|
|
l = r - 1,
|
|
|
j = i,
|
|
|
child = array[j];
|
|
|
if (l < size && compare(array[l], child) < 0) child = array[j = l];
|
|
|
if (r < size && compare(array[r], child) < 0) child = array[j = r];
|
|
|
if (j === i) break;
|
|
|
array[child._ = i] = child;
|
|
|
array[object._ = i = j] = object;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
return heap;
|
|
|
};
|
|
|
|
|
|
function copy(point) {
|
|
|
return [point[0], point[1], 0];
|
|
|
}
|
|
|
|
|
|
var presimplify = function(topology, weight) {
|
|
|
var point = topology.transform ? transform(topology.transform) : copy,
|
|
|
heap = newHeap();
|
|
|
|
|
|
if (weight == null) weight = planarTriangleArea;
|
|
|
|
|
|
var arcs = topology.arcs.map(function(arc) {
|
|
|
var triangles = [],
|
|
|
maxWeight = 0,
|
|
|
triangle,
|
|
|
i,
|
|
|
n;
|
|
|
|
|
|
arc = arc.map(point);
|
|
|
|
|
|
for (i = 1, n = arc.length - 1; i < n; ++i) {
|
|
|
triangle = [arc[i - 1], arc[i], arc[i + 1]];
|
|
|
triangle[1][2] = weight(triangle);
|
|
|
triangles.push(triangle);
|
|
|
heap.push(triangle);
|
|
|
}
|
|
|
|
|
|
// Always keep the arc endpoints!
|
|
|
arc[0][2] = arc[n][2] = Infinity;
|
|
|
|
|
|
for (i = 0, n = triangles.length; i < n; ++i) {
|
|
|
triangle = triangles[i];
|
|
|
triangle.previous = triangles[i - 1];
|
|
|
triangle.next = triangles[i + 1];
|
|
|
}
|
|
|
|
|
|
while (triangle = heap.pop()) {
|
|
|
var previous = triangle.previous,
|
|
|
next = triangle.next;
|
|
|
|
|
|
// If the weight of the current point is less than that of the previous
|
|
|
// point to be eliminated, use the latter’s weight instead. This ensures
|
|
|
// that the current point cannot be eliminated without eliminating
|
|
|
// previously- eliminated points.
|
|
|
if (triangle[1][2] < maxWeight) triangle[1][2] = maxWeight;
|
|
|
else maxWeight = triangle[1][2];
|
|
|
|
|
|
if (previous) {
|
|
|
previous.next = next;
|
|
|
previous[2] = triangle[2];
|
|
|
update(previous);
|
|
|
}
|
|
|
|
|
|
if (next) {
|
|
|
next.previous = previous;
|
|
|
next[0] = triangle[0];
|
|
|
update(next);
|
|
|
}
|
|
|
}
|
|
|
|
|
|
return arc;
|
|
|
});
|
|
|
|
|
|
function update(triangle) {
|
|
|
heap.remove(triangle);
|
|
|
triangle[1][2] = weight(triangle);
|
|
|
heap.push(triangle);
|
|
|
}
|
|
|
|
|
|
return {
|
|
|
type: "Topology",
|
|
|
bbox: topology.bbox,
|
|
|
objects: topology.objects,
|
|
|
arcs: arcs
|
|
|
};
|
|
|
};
|
|
|
|
|
|
var quantile = function(topology, p) {
|
|
|
var array = [];
|
|
|
|
|
|
topology.arcs.forEach(function(arc) {
|
|
|
arc.forEach(function(point) {
|
|
|
if (isFinite(point[2])) { // Ignore endpoints, whose weight is Infinity.
|
|
|
array.push(point[2]);
|
|
|
}
|
|
|
});
|
|
|
});
|
|
|
|
|
|
return array.length && quantile$1(array.sort(descending), p);
|
|
|
};
|
|
|
|
|
|
function quantile$1(array, p) {
|
|
|
if (!(n = array.length)) return;
|
|
|
if ((p = +p) <= 0 || n < 2) return array[0];
|
|
|
if (p >= 1) return array[n - 1];
|
|
|
var n,
|
|
|
h = (n - 1) * p,
|
|
|
i = Math.floor(h),
|
|
|
a = array[i],
|
|
|
b = array[i + 1];
|
|
|
return a + (b - a) * (h - i);
|
|
|
}
|
|
|
|
|
|
function descending(a, b) {
|
|
|
return b - a;
|
|
|
}
|
|
|
|
|
|
var simplify = function(topology, minWeight) {
|
|
|
minWeight = minWeight == null ? Number.MIN_VALUE : +minWeight;
|
|
|
|
|
|
// Remove points whose weight is less than the minimum weight.
|
|
|
var arcs = topology.arcs.map(function(input) {
|
|
|
var i = -1,
|
|
|
j = 0,
|
|
|
n = input.length,
|
|
|
output = new Array(n), // pessimistic
|
|
|
point;
|
|
|
|
|
|
while (++i < n) {
|
|
|
if ((point = input[i])[2] >= minWeight) {
|
|
|
output[j++] = [point[0], point[1]];
|
|
|
}
|
|
|
}
|
|
|
|
|
|
output.length = j;
|
|
|
return output;
|
|
|
});
|
|
|
|
|
|
return {
|
|
|
type: "Topology",
|
|
|
transform: topology.transform,
|
|
|
bbox: topology.bbox,
|
|
|
objects: topology.objects,
|
|
|
arcs: arcs
|
|
|
};
|
|
|
};
|
|
|
|
|
|
var pi = Math.PI;
|
|
|
var tau = 2 * pi;
|
|
|
var quarterPi = pi / 4;
|
|
|
var radians = pi / 180;
|
|
|
var abs = Math.abs;
|
|
|
var atan2 = Math.atan2;
|
|
|
var cos = Math.cos;
|
|
|
var sin = Math.sin;
|
|
|
|
|
|
function halfArea(ring, closed) {
|
|
|
var i = 0,
|
|
|
n = ring.length,
|
|
|
sum = 0,
|
|
|
point = ring[closed ? i++ : n - 1],
|
|
|
lambda0, lambda1 = point[0] * radians,
|
|
|
phi1 = (point[1] * radians) / 2 + quarterPi,
|
|
|
cosPhi0, cosPhi1 = cos(phi1),
|
|
|
sinPhi0, sinPhi1 = sin(phi1);
|
|
|
|
|
|
for (; i < n; ++i) {
|
|
|
point = ring[i];
|
|
|
lambda0 = lambda1, lambda1 = point[0] * radians;
|
|
|
phi1 = (point[1] * radians) / 2 + quarterPi;
|
|
|
cosPhi0 = cosPhi1, cosPhi1 = cos(phi1);
|
|
|
sinPhi0 = sinPhi1, sinPhi1 = sin(phi1);
|
|
|
|
|
|
// Spherical excess E for a spherical triangle with vertices: south pole,
|
|
|
// previous point, current point. Uses a formula derived from Cagnoli’s
|
|
|
// theorem. See Todhunter, Spherical Trig. (1871), Sec. 103, Eq. (2).
|
|
|
// See https://github.com/d3/d3-geo/blob/master/README.md#geoArea
|
|
|
var dLambda = lambda1 - lambda0,
|
|
|
sdLambda = dLambda >= 0 ? 1 : -1,
|
|
|
adLambda = sdLambda * dLambda,
|
|
|
k = sinPhi0 * sinPhi1,
|
|
|
u = cosPhi0 * cosPhi1 + k * cos(adLambda),
|
|
|
v = k * sdLambda * sin(adLambda);
|
|
|
sum += atan2(v, u);
|
|
|
}
|
|
|
|
|
|
return sum;
|
|
|
}
|
|
|
|
|
|
function sphericalRingArea(ring, interior) {
|
|
|
var sum = halfArea(ring, true);
|
|
|
if (interior) sum *= -1;
|
|
|
return (sum < 0 ? tau + sum : sum) * 2;
|
|
|
}
|
|
|
|
|
|
function sphericalTriangleArea(t) {
|
|
|
return abs(halfArea(t, false)) * 2;
|
|
|
}
|
|
|
|
|
|
exports.bbox = bbox;
|
|
|
exports.feature = feature;
|
|
|
exports.mesh = mesh;
|
|
|
exports.meshArcs = meshArcs;
|
|
|
exports.merge = merge;
|
|
|
exports.mergeArcs = mergeArcs;
|
|
|
exports.neighbors = neighbors;
|
|
|
exports.quantize = quantize;
|
|
|
exports.transform = transform;
|
|
|
exports.untransform = untransform;
|
|
|
exports.topology = topology;
|
|
|
exports.filter = filter;
|
|
|
exports.filterAttached = filterAttached;
|
|
|
exports.filterAttachedWeight = filterAttachedWeight;
|
|
|
exports.filterWeight = filterWeight;
|
|
|
exports.planarRingArea = planarRingArea$1;
|
|
|
exports.planarTriangleArea = planarTriangleArea;
|
|
|
exports.presimplify = presimplify;
|
|
|
exports.quantile = quantile;
|
|
|
exports.simplify = simplify;
|
|
|
exports.sphericalRingArea = sphericalRingArea;
|
|
|
exports.sphericalTriangleArea = sphericalTriangleArea;
|
|
|
|
|
|
Object.defineProperty(exports, '__esModule', { value: true });
|
|
|
|
|
|
})));
|
|
|
|
|
|
export default tmp.topojson;
|