|
// https://github.com/topojson/topojson-client Version 3.0.0. Copyright 2017 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'; |
|
|
|
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 collection = function(topology, o) { |
|
return o.type === "GeometryCollection" |
|
? {type: "FeatureCollection", features: o.geometries.map(function(o) { return feature(topology, o); })} |
|
: feature(topology, o); |
|
}; |
|
|
|
function feature(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 meshes(topology) { |
|
return collection(topology, { |
|
type: "GeometryCollection", |
|
geometries: meshesArcs.apply(this, arguments) |
|
}); |
|
} |
|
|
|
function meshArcs(topology, object$$1, filter) { |
|
var p, partition = meshesArcs(topology, object$$1, !filter ? null : function() { |
|
return !!filter.apply(this, arguments); |
|
}); |
|
return partition.length ? (p = partition[0], delete p.properties, p) : { type: 'MultiLineString', arcs: [] }; |
|
} |
|
|
|
function meshesArcs(topology, object$$1, tag) { |
|
var partition, arcs, tags, i, n; |
|
|
|
if (arguments.length > 1) partition = extractArcs(topology, object$$1, tag), tags = partition[1], partition = partition[0]; |
|
else for (i = 0, arcs = new Array(n = topology.arcs.length); i < n; ++i) arcs[i] = i, partition = [arcs], tags = [true]; |
|
|
|
return partition.map(function(arcs, i) { |
|
return { |
|
type: "MultiLineString", |
|
properties: { tag: tags[i] }, |
|
arcs: stitch(topology, arcs) |
|
} |
|
}); |
|
} |
|
|
|
function extractArcs(topology, object$$1, tag) { |
|
var tags = [], |
|
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 tagpush(i, tag) { |
|
if (!tag) return; |
|
var t = tags.indexOf(tag); |
|
if (t === -1) tags.push(tag), t = arcs.length, arcs.push([]); |
|
arcs[t].push(i); |
|
} |
|
|
|
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(function(geoms) { |
|
tagpush(geoms[0].i, tag ? tag(geoms[0].g, geoms[geoms.length - 1].g) : true); |
|
}); |
|
|
|
return [arcs, tags]; |
|
} |
|
|
|
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 ≥2"); |
|
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) |
|
}; |
|
}; |
|
|
|
exports.bbox = bbox; |
|
exports.feature = collection; |
|
exports.mesh = mesh; |
|
exports.meshes = meshes; |
|
exports.meshArcs = meshArcs; |
|
exports.meshesArcs = meshesArcs; |
|
exports.merge = merge; |
|
exports.mergeArcs = mergeArcs; |
|
exports.neighbors = neighbors; |
|
exports.quantize = quantize; |
|
exports.transform = transform; |
|
exports.untransform = untransform; |
|
|
|
Object.defineProperty(exports, '__esModule', { value: true }); |
|
|
|
}))); |