PortaleOrdiniGruppo/PortalStudio/topojson.js
2025-03-24 15:28:26 +01:00

1911 lines
53 KiB
JavaScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// https://github.com/topojson/topojson Version 2.2.0. Copyright 2016 Mike Bostock.
(function (global, factory) {
typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports) :
typeof define === 'function' && define.amd ? define(['exports'], factory) :
(factory((global.topojson = global.topojson || {})));
}(this, (function (exports) { 'use strict';
// 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 && 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.coordinates); },
MultiLineString: function(o) { o.coordinates.forEach(boundLine); },
Polygon: function(o) { o.coordinates.forEach(boundLine); },
MultiPolygon: function(o) { o.coordinates.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 floats = new Float64Array(buffer);
var uints = new Uint32Array(buffer);
var hashPoint = function(point) {
floats[0] = point[0];
floats[1] = point[1];
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 As 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(array, start, end);
reverse(array, start, start + offset);
reverse(array, start + offset, end);
}
function reverse(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 a TopoJSON topology in absolute (quantized) coordinates,
// converts to fixed-point delta encoding.
// This is a destructive operation that modifies the given topology!
var delta = function(topology) {
var arcs = topology.arcs,
i = -1,
n = arcs.length;
while (++i < n) {
var arc = arcs[i],
j = 0,
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];
arc[j] = [x1 - x0, y1 - y0];
x0 = x1;
y0 = y1;
}
}
return topology;
};
// 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.coordinates); delete o.coordinates; },
MultiLineString: function(o) { o.arcs = o.coordinates.map(extractLine); delete o.coordinates; },
Polygon: function(o) { o.arcs = o.coordinates.map(extractRing); delete o.coordinates; },
MultiPolygon: function(o) { o.arcs = o.coordinates.map(extractMultiRing); delete o.coordinates; }
};
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, replaces Features with geometry objects.
// This is a destructive operation that modifies the input objects!
var geometry = function(objects) {
var key;
for (key in objects) objects[key] = geomifyObject(objects[key]);
return objects;
};
function geomifyObject(object) {
return (object && geomifyObjectType.hasOwnProperty(object.type)
? geomifyObjectType[object.type]
: geomifyGeometry)(object);
}
function geomifyFeature(feature) {
var geometry = feature.geometry;
if (geometry == null) {
feature.type = null;
} else {
geomifyGeometry(geometry);
feature.type = geometry.type;
if (geometry.geometries) feature.geometries = geometry.geometries;
else if (geometry.coordinates) feature.coordinates = geometry.coordinates;
if (geometry.bbox) feature.bbox = geometry.bbox;
}
delete feature.geometry;
return feature;
}
function geomifyGeometry(geometry) {
if (!geometry) return {type: null};
if (geomifyGeometryType.hasOwnProperty(geometry.type)) geomifyGeometryType[geometry.type](geometry);
return geometry;
}
var geomifyObjectType = {
Feature: geomifyFeature,
FeatureCollection: function(collection) {
collection.type = "GeometryCollection";
collection.geometries = collection.features;
collection.features.forEach(geomifyFeature);
delete collection.features;
return collection;
}
};
var geomifyGeometryType = {
GeometryCollection: function(o) {
var geometries = o.geometries, i = -1, n = geometries.length;
while (++i < n) geometries[i] = geomifyGeometry(geometries[i]);
},
MultiPoint: function(o) {
if (!o.coordinates.length) {
o.type = null;
delete o.coordinates;
} else if (o.coordinates.length < 2) {
o.type = "Point";
o.coordinates = o.coordinates[0];
}
},
LineString: function(o) {
if (!o.coordinates.length) {
o.type = null;
delete o.coordinates;
}
},
MultiLineString: function(o) {
for (var lines = o.coordinates, i = 0, N = 0, n = lines.length; i < n; ++i) {
var line = lines[i];
if (line.length) lines[N++] = line;
}
if (!N) {
o.type = null;
delete o.coordinates;
} else if (N < 2) {
o.type = "LineString";
o.coordinates = lines[0];
} else {
o.coordinates.length = N;
}
},
Polygon: function(o) {
for (var rings = o.coordinates, i = 0, N = 0, n = rings.length; i < n; ++i) {
var ring = rings[i];
if (ring.length) rings[N++] = ring;
}
if (!N) {
o.type = null;
delete o.coordinates;
} else {
o.coordinates.length = N;
}
},
MultiPolygon: function(o) {
for (var polygons = o.coordinates, j = 0, M = 0, m = polygons.length; j < m; ++j) {
for (var rings = polygons[j], i = 0, N = 0, n = rings.length; i < n; ++i) {
var ring = rings[i];
if (ring.length) rings[N++] = ring;
}
if (N) {
rings.length = N;
polygons[M++] = rings;
}
}
if (!M) {
o.type = null;
delete o.coordinates;
} else if (M < 2) {
o.type = "Polygon";
o.coordinates = polygons[0];
} else {
polygons.length = M;
}
}
};
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(coordinates) {
coordinates[0] = Math.round((coordinates[0] - x0) * kx);
coordinates[1] = Math.round((coordinates[1] - y0) * ky);
return coordinates;
}
function quantizeLine(coordinates) {
var i = 0,
j = 1,
n = coordinates.length,
pi = quantizePoint(coordinates[0]),
pj,
px = pi[0],
py = pi[1],
x,
y;
while (++i < n) {
pi = quantizePoint(coordinates[i]);
x = pi[0];
y = pi[1];
if (x !== px || y !== py) { // skip coincident points
pj = coordinates[j++];
pj[0] = px = x;
pj[1] = py = y;
}
}
coordinates.length = j;
}
function quantizeGeometry(o) {
if (o && quantizeGeometryType.hasOwnProperty(o.type)) quantizeGeometryType[o.type](o);
}
var quantizeGeometryType = {
GeometryCollection: function(o) {
o.geometries.forEach(quantizeGeometry);
},
Point: function(o) {
quantizePoint(o.coordinates);
},
MultiPoint: function(o) {
o.coordinates.forEach(quantizePoint);
},
LineString: function(o) {
var line = o.coordinates;
quantizeLine(line);
if (line.length < 2) line[1] = line[0]; // must have 2+
},
MultiLineString: function(o) {
for (var lines = o.coordinates, i = 0, n = lines.length; i < n; ++i) {
var line = lines[i];
quantizeLine(line);
if (line.length < 2) line[1] = line[0]; // must have 2+
}
},
Polygon: function(o) {
for (var rings = o.coordinates, i = 0, n = rings.length; i < n; ++i) {
var ring = rings[i];
quantizeLine(ring);
while (ring.length < 4) ring.push(ring[0]); // must have 4+
}
},
MultiPolygon: function(o) {
for (var polygons = o.coordinates, i = 0, n = polygons.length; i < n; ++i) {
for (var rings = polygons[i], j = 0, m = rings.length; j < m; ++j) {
var ring = rings[j];
quantizeLine(ring);
while (ring.length < 4) ring.push(ring[0]); // must have 4+
}
}
}
};
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(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;
delta(topology);
}
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 oldArcs = topology.arcs,
newArcs = topology.arcs = [],
newArcIndex = -1,
newIndexByOldIndex = new Array(oldArcs.length),
name;
function pruneGeometry(o) {
switch (o.type) {
case "GeometryCollection": o.geometries.forEach(pruneGeometry); break;
case "LineString": pruneArcs(o.arcs); break;
case "MultiLineString": o.arcs.forEach(pruneArcs); break;
case "Polygon": o.arcs.forEach(pruneArcs); break;
case "MultiPolygon": o.arcs.forEach(pruneMultiArcs); break;
}
}
function pruneArcs(arcs) {
for (var i = 0, n = arcs.length; i < n; ++i) {
var oldIndex = arcs[i],
oldReverse = oldIndex < 0 && (oldIndex = ~oldIndex, true),
newIndex;
// If this is the first instance of this arc,
// record it under its new index.
if ((newIndex = newIndexByOldIndex[oldIndex]) == null) {
newIndexByOldIndex[oldIndex] = newIndex = ++newArcIndex;
newArcs[newIndex] = oldArcs[oldIndex];
}
arcs[i] = oldReverse ? ~newIndex : newIndex;
}
}
function pruneMultiArcs(arcs) {
arcs.forEach(pruneArcs);
}
for (name in topology.objects) {
pruneGeometry(topology.objects[name]);
}
return topology;
};
var filter = function(topology, filter) {
var name;
if (filter == null) filter = filterTrue;
function filterGeometry(o) {
switch (o.type) {
case "Polygon": {
o.arcs = filterRings(o.arcs);
if (!o.arcs) o.type = null, delete o.arcs;
break;
}
case "MultiPolygon": {
o.arcs = o.arcs.map(filterRings).filter(filterIdentity);
if (!o.arcs.length) o.type = null, delete o.arcs;
break;
}
case "GeometryCollection": {
o.geometries.forEach(filterGeometry);
o.geometries = o.geometries.filter(filterNotNull);
if (!o.geometries.length) o.type = null, delete o.geometries;
break;
}
}
}
function filterRings(arcs) {
return arcs.length && filterExteriorRing(arcs[0]) // if the exterior is small, ignore any holes
? [arcs.shift()].concat(arcs.filter(filterInteriorRing))
: null;
}
function filterExteriorRing(ring) {
return filter(ring, false);
}
function filterInteriorRing(ring) {
return filter(ring, true);
}
for (name in topology.objects) {
filterGeometry(topology.objects[name]);
}
return prune(topology);
};
function filterTrue() {
return true;
}
function filterIdentity(x) {
return x;
}
function filterNotNull(geometry) {
return geometry.type != null;
}
var filterAttached = function(topology) {
var uniqueRingByArc = {}, // arc index -> index of unique associated ring, or -1 if used by multiple rings
ringIndex = 0,
name;
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, ++ringIndex) {
for (var ring = arcs[i], j = 0, m = ring.length; j < m; ++j) {
var arc = ring[j];
if (arc < 0) arc = ~arc;
var uniqueRing = uniqueRingByArc[arc];
if (uniqueRing >= 0 && uniqueRing !== ringIndex) uniqueRingByArc[arc] = -1;
else uniqueRingByArc[arc] = ringIndex;
}
}
}
for (name in topology.objects) {
testGeometry(topology.objects[name]);
}
return function(ring) {
for (var j = 0, m = ring.length, arc; j < m; ++j) {
if (arc = ring[j], uniqueRingByArc[arc < 0 ? ~arc : arc] < 0) {
return true;
}
}
return false;
};
};
var identity = function(x) {
return x;
};
var transform = function(topology) {
if ((transform = topology.transform) == null) return identity;
var transform,
x0,
y0,
kx = transform.scale[0],
ky = transform.scale[1],
dx = transform.translate[0],
dy = transform.translate[1];
return function(point, i) {
if (!i) x0 = y0 = 0;
point[0] = (x0 += point[0]) * kx + dx;
point[1] = (y0 += point[1]) * ky + dy;
return point;
};
};
var bbox = function(topology) {
var bbox = topology.bbox;
function bboxPoint(p0) {
p1[0] = p0[0], p1[1] = p0[1], t(p1);
if (p1[0] < x0) x0 = p1[0];
if (p1[0] > x1) x1 = p1[0];
if (p1[1] < y0) y0 = p1[1];
if (p1[1] > y1) y1 = p1[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;
}
}
if (!bbox) {
var t = transform(topology), p0, p1 = new Array(2), name,
x0 = Infinity, y0 = x0, x1 = -x0, y1 = -x0;
topology.arcs.forEach(function(arc) {
var i = -1, n = arc.length;
while (++i < n) {
p0 = arc[i], p1[0] = p0[0], p1[1] = p0[1], t(p1, i);
if (p1[0] < x0) x0 = p1[0];
if (p1[0] > x1) x1 = p1[0];
if (p1[1] < y0) y0 = p1[1];
if (p1[1] > y1) y1 = p1[1];
}
});
for (name in topology.objects) {
bboxGeometry(topology.objects[name]);
}
bbox = topology.bbox = [x0, y0, x1, y1];
}
return bbox;
};
var reverse$1 = 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),
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].slice(), k));
}
if (i < 0) reverse$1(points, n);
}
function point(p) {
return transformPoint(p.slice());
}
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].slice());
return points;
}
function ring(arcs) {
var points = line(arcs);
while (points.length < 4) points.push(points[0].slice());
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 quantize = function(topology, n) {
if (!((n = Math.floor(n)) >= 2)) throw new Error("n must be \u22652");
if (topology.transform) throw new Error("already quantized");
var bb = bbox(topology), name,
dx = bb[0], kx = (bb[2] - dx) / (n - 1) || 1,
dy = bb[1], ky = (bb[3] - dy) / (n - 1) || 1;
function quantizePoint(p) {
p[0] = Math.round((p[0] - dx) / kx);
p[1] = Math.round((p[1] - dy) / ky);
}
function quantizeGeometry(o) {
switch (o.type) {
case "GeometryCollection": o.geometries.forEach(quantizeGeometry); break;
case "Point": quantizePoint(o.coordinates); break;
case "MultiPoint": o.coordinates.forEach(quantizePoint); break;
}
}
topology.arcs.forEach(function(arc) {
var i = 1,
j = 1,
n = arc.length,
pi = arc[0],
x0 = pi[0] = Math.round((pi[0] - dx) / kx),
y0 = pi[1] = Math.round((pi[1] - dy) / ky),
pj,
x1,
y1;
for (; i < n; ++i) {
pi = arc[i];
x1 = Math.round((pi[0] - dx) / kx);
y1 = Math.round((pi[1] - dy) / ky);
if (x1 !== x0 || y1 !== y0) {
pj = arc[j++];
pj[0] = x1 - x0, x0 = x1;
pj[1] = y1 - y0, y0 = y1;
}
}
if (j < 2) {
pj = arc[j++];
pj[0] = 0;
pj[1] = 0;
}
arc.length = j;
});
for (name in topology.objects) {
quantizeGeometry(topology.objects[name]);
}
topology.transform = {
scale: [kx, ky],
translate: [dx, dy]
};
return topology;
};
var untransform = function(topology) {
if ((transform = topology.transform) == null) return identity;
var transform,
x0,
y0,
kx = transform.scale[0],
ky = transform.scale[1],
dx = transform.translate[0],
dy = transform.translate[1];
return function(point, i) {
if (!i) x0 = y0 = 0;
var x1 = Math.round((point[0] - dx) / kx),
y1 = Math.round((point[1] - dy) / ky);
point[0] = x1 - x0, x0 = x1;
point[1] = y1 - y0, y0 = y1;
return point;
};
};
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]));
}
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;
};
};
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;
};
var presimplify = function(topology, weight) {
var absolute = transform(topology),
relative = untransform(topology),
heap = newHeap();
if (weight == null) weight = planarTriangleArea;
topology.arcs.forEach(function(arc) {
var triangles = [],
maxWeight = 0,
triangle,
i,
n;
arc.forEach(absolute);
for (i = 1, n = arc.length - 1; i < n; ++i) {
triangle = arc.slice(i - 1, i + 2);
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 latters 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);
}
}
arc.forEach(relative);
});
function update(triangle) {
heap.remove(triangle);
triangle[1][2] = weight(triangle);
heap.push(triangle);
}
return topology;
};
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.
topology.arcs.forEach(topology.transform ? function(arc) {
var dx = 0,
dy = 0, // accumulate removed points
i = -1,
j = -1,
n = arc.length,
source,
target;
while (++i < n) {
source = arc[i];
if (source[2] >= minWeight) {
target = arc[++j];
target[0] = source[0] + dx;
target[1] = source[1] + dy;
dx = dy = 0;
} else {
dx += source[0];
dy += source[1];
}
}
arc.length = ++j;
} : function(arc) {
var i = -1,
j = -1,
n = arc.length,
point;
while (++i < n) {
point = arc[i];
if (point[2] >= minWeight) {
arc[++j] = point;
}
}
arc.length = ++j;
});
// Remove the computed weight for each point, and remove coincident points.
// This is done as a separate pass because some coordinates may be shared
// between arcs (such as the last point and first point of a cut line).
// If the entire arc is empty, retain at least two points (per spec).
topology.arcs.forEach(topology.transform ? function(arc) {
var i = 0,
j = 0,
n = arc.length,
p = arc[0];
p.length = 2;
while (++i < n) {
p = arc[i];
p.length = 2;
if (p[0] || p[1]) arc[++j] = p;
}
arc.length = (j || 1) + 1;
} : function(arc) {
var i = 0,
j = 0,
n = arc.length,
p = arc[0],
x0 = p[0],
y0 = p[1],
x1,
y1;
p.length = 2;
while (++i < n) {
p = arc[i], x1 = p[0], y1 = p[1];
p.length = 2;
if (x0 !== x1 || y0 !== y1) arc[++j] = p, x0 = x1, y0 = y1;
}
arc.length = (j || 1) + 1;
});
return topology;
};
var pi = Math.PI;
var tau = 2 * pi;
var fourPi = 4 * pi;
var radians = pi / 180;
var abs = Math.abs;
var atan = Math.atan;
var atan2 = Math.atan2;
var cos = Math.cos;
var max = Math.max;
var sin = Math.sin;
var sqrt = Math.sqrt;
var tan = Math.tan;
function sphericalRingArea(ring, interior) {
if (!ring.length) return 0;
var sum = 0,
point = ring[0],
lambda0, lambda1 = point[0] * radians, delta,
phi1 = (point[1] * radians + tau) / 2,
cosPhi0, cosPhi1 = cos(phi1),
sinPhi0, sinPhi1 = sin(phi1),
i, n, k;
for (i = 1, n = ring.length; i < n; ++i) {
point = ring[i];
lambda0 = lambda1, lambda1 = point[0] * radians, delta = lambda1 - lambda0;
phi1 = (point[1] * radians + tau) / 2;
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 Cagnolis
// theorem. See Todhunter, Spherical Trig. (1871), Sec. 103, Eq. (2).
k = sinPhi0 * sinPhi1;
sum += atan2(k * sin(delta), cosPhi0 * cosPhi1 + k * cos(delta));
}
sum = 2 * (sum > pi ? sum - tau : sum < -pi ? sum + tau : sum);
if (interior) sum *= -1;
return sum < 0 ? sum + fourPi : sum;
}
function sphericalTriangleArea(t) {
var lambda0 = t[0][0] * radians, phi0 = t[0][1] * radians, cosPhi0 = cos(phi0), sinPhi0 = sin(phi0),
lambda1 = t[1][0] * radians, phi1 = t[1][1] * radians, cosPhi1 = cos(phi1), sinPhi1 = sin(phi1),
lambda2 = t[2][0] * radians, phi2 = t[2][1] * radians, cosPhi2 = cos(phi2), sinPhi2 = sin(phi2),
a = distance(lambda0, cosPhi0, sinPhi0, lambda1, cosPhi1, sinPhi1),
b = distance(lambda1, cosPhi1, sinPhi1, lambda2, cosPhi2, sinPhi2),
c = distance(lambda2, cosPhi2, sinPhi2, lambda0, cosPhi0, sinPhi0),
s = (a + b + c) / 2;
return 4 * atan(sqrt(max(0, tan(s / 2) * tan((s - a) / 2) * tan((s - b) / 2) * tan((s - c) / 2))));
}
function distance(lambda0, sinPhi0, cosPhi0, lambda1, sinPhi1, cosPhi1) {
var delta = abs(lambda1 - lambda0),
cosDelta = cos(delta),
sinDelta = sin(delta),
x = cosPhi1 * sinDelta,
y = cosPhi0 * sinPhi1 - sinPhi0 * cosPhi1 * cosDelta,
z = sinPhi0 * sinPhi1 + cosPhi0 * cosPhi1 * cosDelta;
return atan2(sqrt(x * x + y * y), z);
}
exports.topology = topology;
exports.filter = filter;
exports.filterAttached = filterAttached;
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;
exports.bbox = bbox;
exports.feature = feature;
exports.merge = merge;
exports.mergeArcs = mergeArcs;
exports.mesh = mesh;
exports.meshArcs = meshArcs;
exports.neighbors = neighbors;
exports.quantize = quantize;
exports.transform = transform;
exports.untransform = untransform;
Object.defineProperty(exports, '__esModule', { value: true });
})));