839 lines
25 KiB
JavaScript
839 lines
25 KiB
JavaScript
// https://github.com/topojson/topojson-server v3.0.1 Copyright 2019 Mike Bostock
|
||
(function (global, factory) {
|
||
typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports) :
|
||
typeof define === 'function' && define.amd ? define(['exports'], factory) :
|
||
(global = global || self, factory(global.topojson = global.topojson || {}));
|
||
}(this, function (exports) { 'use strict';
|
||
|
||
var hasOwnProperty = Object.prototype.hasOwnProperty;
|
||
|
||
// Computes the bounding box of the specified hash of GeoJSON objects.
|
||
function bounds(objects) {
|
||
var x0 = Infinity,
|
||
y0 = Infinity,
|
||
x1 = -Infinity,
|
||
y1 = -Infinity;
|
||
|
||
function boundGeometry(geometry) {
|
||
if (geometry != null && hasOwnProperty.call(boundGeometryType, 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;
|
||
}
|
||
|
||
function hashset(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
|
||
};
|
||
}
|
||
|
||
function hashmap(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
|
||
};
|
||
}
|
||
|
||
function equalPoint(pointA, pointB) {
|
||
return pointA[0] === pointB[0] && pointA[1] === pointB[1];
|
||
}
|
||
|
||
// TODO if quantized, use simpler Int32 hashing?
|
||
|
||
var buffer = new ArrayBuffer(16),
|
||
floats = new Float64Array(buffer),
|
||
uints = new Uint32Array(buffer);
|
||
|
||
function hashPoint(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 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.
|
||
function join(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.
|
||
function cut(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.
|
||
function dedup(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!
|
||
function delta(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.
|
||
function extract(objects) {
|
||
var index = -1,
|
||
lines = [],
|
||
rings = [],
|
||
coordinates = [];
|
||
|
||
function extractGeometry(geometry) {
|
||
if (geometry && hasOwnProperty.call(extractGeometryType, 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)!
|
||
function geometry(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;
|
||
}
|
||
|
||
function prequantize(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 && hasOwnProperty.call(quantizeGeometryType, 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.
|
||
function topology(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) {
|
||
if (geometry && hasOwnProperty.call(indexGeometryType, geometry.type)) indexGeometryType[geometry.type](geometry);
|
||
}
|
||
|
||
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;
|
||
}
|
||
|
||
exports.topology = topology;
|
||
|
||
Object.defineProperty(exports, '__esModule', { value: true });
|
||
|
||
}));
|