package.format.TopoJSON.js Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of ol Show documentation
Show all versions of ol Show documentation
OpenLayers mapping library
The newest version!
/**
* @module ol/format/TopoJSON
*/
import Feature from '../Feature.js';
import JSONFeature from './JSONFeature.js';
import LineString from '../geom/LineString.js';
import MultiLineString from '../geom/MultiLineString.js';
import MultiPoint from '../geom/MultiPoint.js';
import MultiPolygon from '../geom/MultiPolygon.js';
import Point from '../geom/Point.js';
import Polygon from '../geom/Polygon.js';
import {get as getProjection} from '../proj.js';
import {transformGeometryWithOptions} from './Feature.js';
/**
* @typedef {import("topojson-specification").Topology} TopoJSONTopology
* @typedef {import("topojson-specification").GeometryCollection} TopoJSONGeometryCollection
* @typedef {import("topojson-specification").GeometryObject} TopoJSONGeometry
* @typedef {import("topojson-specification").Point} TopoJSONPoint
* @typedef {import("topojson-specification").MultiPoint} TopoJSONMultiPoint
* @typedef {import("topojson-specification").LineString} TopoJSONLineString
* @typedef {import("topojson-specification").MultiLineString} TopoJSONMultiLineString
* @typedef {import("topojson-specification").Polygon} TopoJSONPolygon
* @typedef {import("topojson-specification").MultiPolygon} TopoJSONMultiPolygon
*/
/**
* @typedef {Object} Options
* @property {import("../proj.js").ProjectionLike} [dataProjection='EPSG:4326'] Default data projection.
* @property {string} [layerName] Set the name of the TopoJSON topology
* `objects`'s children as feature property with the specified name. This means
* that when set to `'layer'`, a topology like
* ```
* {
* "type": "Topology",
* "objects": {
* "example": {
* "type": "GeometryCollection",
* "geometries": []
* }
* }
* }
* ```
* will result in features that have a property `'layer'` set to `'example'`.
* When not set, no property will be added to features.
* @property {Array} [layers] Names of the TopoJSON topology's
* `objects`'s children to read features from. If not provided, features will
* be read from all children.
*/
/**
* @classdesc
* Feature format for reading data in the TopoJSON format.
*
* @api
*/
class TopoJSON extends JSONFeature {
/**
* @param {Options} [options] Options.
*/
constructor(options) {
super();
options = options ? options : {};
/**
* @private
* @type {string|undefined}
*/
this.layerName_ = options.layerName;
/**
* @private
* @type {?Array}
*/
this.layers_ = options.layers ? options.layers : null;
/**
* @type {import("../proj/Projection.js").default}
*/
this.dataProjection = getProjection(
options.dataProjection ? options.dataProjection : 'EPSG:4326',
);
}
/**
* @param {Object} object Object.
* @param {import("./Feature.js").ReadOptions} [options] Read options.
* @protected
* @return {Array} Features.
* @override
*/
readFeaturesFromObject(object, options) {
if (object.type == 'Topology') {
const topoJSONTopology = /** @type {TopoJSONTopology} */ (object);
let transform,
scale = null,
translate = null;
if (topoJSONTopology['transform']) {
transform = topoJSONTopology['transform'];
scale = transform['scale'];
translate = transform['translate'];
}
const arcs = topoJSONTopology['arcs'];
if (transform) {
transformArcs(arcs, scale, translate);
}
/** @type {Array} */
const features = [];
const topoJSONFeatures = topoJSONTopology['objects'];
const property = this.layerName_;
let feature;
for (const objectName in topoJSONFeatures) {
if (this.layers_ && !this.layers_.includes(objectName)) {
continue;
}
if (topoJSONFeatures[objectName].type === 'GeometryCollection') {
feature = /** @type {TopoJSONGeometryCollection} */ (
topoJSONFeatures[objectName]
);
features.push.apply(
features,
readFeaturesFromGeometryCollection(
feature,
arcs,
scale,
translate,
property,
objectName,
options,
),
);
} else {
feature = /** @type {TopoJSONGeometry} */ (
topoJSONFeatures[objectName]
);
features.push(
readFeatureFromGeometry(
feature,
arcs,
scale,
translate,
property,
objectName,
options,
),
);
}
}
return features;
}
return [];
}
/**
* @param {Object} object Object.
* @protected
* @return {import("../proj/Projection.js").default} Projection.
* @override
*/
readProjectionFromObject(object) {
return this.dataProjection;
}
}
/**
* @const
* @type {Object}
*/
const GEOMETRY_READERS = {
'Point': readPointGeometry,
'LineString': readLineStringGeometry,
'Polygon': readPolygonGeometry,
'MultiPoint': readMultiPointGeometry,
'MultiLineString': readMultiLineStringGeometry,
'MultiPolygon': readMultiPolygonGeometry,
};
/**
* Concatenate arcs into a coordinate array.
* @param {Array} indices Indices of arcs to concatenate. Negative
* values indicate arcs need to be reversed.
* @param {Array>} arcs Array of arcs (already
* transformed).
* @return {Array} Coordinates array.
*/
function concatenateArcs(indices, arcs) {
/** @type {Array} */
const coordinates = [];
let index;
for (let i = 0, ii = indices.length; i < ii; ++i) {
index = indices[i];
if (i > 0) {
// splicing together arcs, discard last point
coordinates.pop();
}
if (index >= 0) {
// forward arc
const arc = arcs[index];
for (let j = 0, jj = arc.length; j < jj; ++j) {
coordinates.push(arc[j].slice(0));
}
} else {
// reverse arc
const arc = arcs[~index];
for (let j = arc.length - 1; j >= 0; --j) {
coordinates.push(arc[j].slice(0));
}
}
}
return coordinates;
}
/**
* Create a point from a TopoJSON geometry object.
*
* @param {TopoJSONPoint} object TopoJSON object.
* @param {Array} scale Scale for each dimension.
* @param {Array} translate Translation for each dimension.
* @return {Point} Geometry.
*/
function readPointGeometry(object, scale, translate) {
const coordinates = object['coordinates'];
if (scale && translate) {
transformVertex(coordinates, scale, translate);
}
return new Point(coordinates);
}
/**
* Create a multi-point from a TopoJSON geometry object.
*
* @param {TopoJSONMultiPoint} object TopoJSON object.
* @param {Array} scale Scale for each dimension.
* @param {Array} translate Translation for each dimension.
* @return {MultiPoint} Geometry.
*/
function readMultiPointGeometry(object, scale, translate) {
const coordinates = object['coordinates'];
if (scale && translate) {
for (let i = 0, ii = coordinates.length; i < ii; ++i) {
transformVertex(coordinates[i], scale, translate);
}
}
return new MultiPoint(coordinates);
}
/**
* Create a linestring from a TopoJSON geometry object.
*
* @param {TopoJSONLineString} object TopoJSON object.
* @param {Array>} arcs Array of arcs.
* @return {LineString} Geometry.
*/
function readLineStringGeometry(object, arcs) {
const coordinates = concatenateArcs(object['arcs'], arcs);
return new LineString(coordinates);
}
/**
* Create a multi-linestring from a TopoJSON geometry object.
*
* @param {TopoJSONMultiLineString} object TopoJSON object.
* @param {Array>} arcs Array of arcs.
* @return {MultiLineString} Geometry.
*/
function readMultiLineStringGeometry(object, arcs) {
const coordinates = [];
for (let i = 0, ii = object['arcs'].length; i < ii; ++i) {
coordinates[i] = concatenateArcs(object['arcs'][i], arcs);
}
return new MultiLineString(coordinates);
}
/**
* Create a polygon from a TopoJSON geometry object.
*
* @param {TopoJSONPolygon} object TopoJSON object.
* @param {Array>} arcs Array of arcs.
* @return {Polygon} Geometry.
*/
function readPolygonGeometry(object, arcs) {
const coordinates = [];
for (let i = 0, ii = object['arcs'].length; i < ii; ++i) {
coordinates[i] = concatenateArcs(object['arcs'][i], arcs);
}
return new Polygon(coordinates);
}
/**
* Create a multi-polygon from a TopoJSON geometry object.
*
* @param {TopoJSONMultiPolygon} object TopoJSON object.
* @param {Array>} arcs Array of arcs.
* @return {MultiPolygon} Geometry.
*/
function readMultiPolygonGeometry(object, arcs) {
const coordinates = [];
for (let i = 0, ii = object['arcs'].length; i < ii; ++i) {
// for each polygon
const polyArray = object['arcs'][i];
const ringCoords = [];
for (let j = 0, jj = polyArray.length; j < jj; ++j) {
// for each ring
ringCoords[j] = concatenateArcs(polyArray[j], arcs);
}
coordinates[i] = ringCoords;
}
return new MultiPolygon(coordinates);
}
/**
* Create features from a TopoJSON GeometryCollection object.
*
* @param {TopoJSONGeometryCollection} collection TopoJSON Geometry
* object.
* @param {Array>} arcs Array of arcs.
* @param {Array} scale Scale for each dimension.
* @param {Array} translate Translation for each dimension.
* @param {string|undefined} property Property to set the `GeometryCollection`'s parent
* object to.
* @param {string} name Name of the `Topology`'s child object.
* @param {import("./Feature.js").ReadOptions} [options] Read options.
* @return {Array} Array of features.
*/
function readFeaturesFromGeometryCollection(
collection,
arcs,
scale,
translate,
property,
name,
options,
) {
const geometries = collection['geometries'];
const features = [];
for (let i = 0, ii = geometries.length; i < ii; ++i) {
features[i] = readFeatureFromGeometry(
geometries[i],
arcs,
scale,
translate,
property,
name,
options,
);
}
return features;
}
/**
* Create a feature from a TopoJSON geometry object.
*
* @param {TopoJSONGeometry} object TopoJSON geometry object.
* @param {Array>} arcs Array of arcs.
* @param {Array} scale Scale for each dimension.
* @param {Array} translate Translation for each dimension.
* @param {string|undefined} property Property to set the `GeometryCollection`'s parent
* object to.
* @param {string} name Name of the `Topology`'s child object.
* @param {import("./Feature.js").ReadOptions} [options] Read options.
* @return {Feature} Feature.
*/
function readFeatureFromGeometry(
object,
arcs,
scale,
translate,
property,
name,
options,
) {
let geometry = null;
const type = object.type;
if (type) {
const geometryReader = GEOMETRY_READERS[type];
if (type === 'Point' || type === 'MultiPoint') {
geometry = geometryReader(object, scale, translate);
} else {
geometry = geometryReader(object, arcs);
}
geometry = transformGeometryWithOptions(geometry, false, options);
}
const feature = new Feature({geometry: geometry});
if (object.id !== undefined) {
feature.setId(object.id);
}
let properties = object.properties;
if (property) {
if (!properties) {
properties = {};
}
properties[property] = name;
}
if (properties) {
feature.setProperties(properties, true);
}
return feature;
}
/**
* Apply a linear transform to array of arcs. The provided array of arcs is
* modified in place.
*
* @param {Array>} arcs Array of arcs.
* @param {Array} scale Scale for each dimension.
* @param {Array} translate Translation for each dimension.
*/
function transformArcs(arcs, scale, translate) {
for (let i = 0, ii = arcs.length; i < ii; ++i) {
transformArc(arcs[i], scale, translate);
}
}
/**
* Apply a linear transform to an arc. The provided arc is modified in place.
*
* @param {Array} arc Arc.
* @param {Array} scale Scale for each dimension.
* @param {Array} translate Translation for each dimension.
*/
function transformArc(arc, scale, translate) {
let x = 0;
let y = 0;
for (let i = 0, ii = arc.length; i < ii; ++i) {
const vertex = arc[i];
x += vertex[0];
y += vertex[1];
vertex[0] = x;
vertex[1] = y;
transformVertex(vertex, scale, translate);
}
}
/**
* Apply a linear transform to a vertex. The provided vertex is modified in
* place.
*
* @param {import("../coordinate.js").Coordinate} vertex Vertex.
* @param {Array} scale Scale for each dimension.
* @param {Array} translate Translation for each dimension.
*/
function transformVertex(vertex, scale, translate) {
vertex[0] = vertex[0] * scale[0] + translate[0];
vertex[1] = vertex[1] * scale[1] + translate[1];
}
export default TopoJSON;