package.format.GMLBase.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/GMLBase
*/
// FIXME Envelopes should not be treated as geometries! readEnvelope_ is part
// of GEOMETRY_PARSERS_ and methods using GEOMETRY_PARSERS_ do not expect
// envelopes/extents, only geometries!
import Feature from '../Feature.js';
import Geometry from '../geom/Geometry.js';
import LineString from '../geom/LineString.js';
import LinearRing from '../geom/LinearRing.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 XMLFeature from './XMLFeature.js';
import {extend} from '../array.js';
import {
getAllTextContent,
getAttributeNS,
makeArrayPusher,
makeReplacer,
parseNode,
pushParseAndPop,
} from '../xml.js';
import {get as getProjection} from '../proj.js';
import {
transformExtentWithOptions,
transformGeometryWithOptions,
} from './Feature.js';
/**
* @const
* @type {string}
*/
export const GMLNS = 'http://www.opengis.net/gml';
/**
* A regular expression that matches if a string only contains whitespace
* characters. It will e.g. match `''`, `' '`, `'\n'` etc.
*
* @const
* @type {RegExp}
*/
const ONLY_WHITESPACE_RE = /^\s*$/;
/**
* @typedef {Object} Options
* @property {Object|string} [featureNS] Feature
* namespace. If not defined will be derived from GML. If multiple
* feature types have been configured which come from different feature
* namespaces, this will be an object with the keys being the prefixes used
* in the entries of featureType array. The values of the object will be the
* feature namespaces themselves. So for instance there might be a featureType
* item `topp:states` in the `featureType` array and then there will be a key
* `topp` in the featureNS object with value `http://www.openplans.org/topp`.
* @property {Array|string} [featureType] Feature type(s) to parse.
* If multiple feature types need to be configured
* which come from different feature namespaces, `featureNS` will be an object
* with the keys being the prefixes used in the entries of featureType array.
* The values of the object will be the feature namespaces themselves.
* So for instance there might be a featureType item `topp:states` and then
* there will be a key named `topp` in the featureNS object with value
* `http://www.openplans.org/topp`.
* @property {string} [srsName] srsName to use when writing geometries.
* @property {boolean} [surface=false] Write gml:Surface instead of gml:Polygon
* elements. This also affects the elements in multi-part geometries.
* @property {boolean} [curve=false] Write gml:Curve instead of gml:LineString
* elements. This also affects the elements in multi-part geometries.
* @property {boolean} [multiCurve=true] Write gml:MultiCurve instead of gml:MultiLineString.
* Since the latter is deprecated in GML 3.
* @property {boolean} [multiSurface=true] Write gml:multiSurface instead of
* gml:MultiPolygon. Since the latter is deprecated in GML 3.
* @property {string} [schemaLocation] Optional schemaLocation to use when
* writing out the GML, this will override the default provided.
* @property {boolean} [hasZ=false] If coordinates have a Z value.
*/
/**
* @classdesc
* Abstract base class; normally only used for creating subclasses and not
* instantiated in apps.
* Feature base format for reading and writing data in the GML format.
* This class cannot be instantiated, it contains only base content that
* is shared with versioned format classes GML2 and GML3.
*
* @abstract
* @api
*/
class GMLBase extends XMLFeature {
/**
* @param {Options} [options] Optional configuration object.
*/
constructor(options) {
super();
options = options ? options : {};
/**
* @protected
* @type {Array|string|undefined}
*/
this.featureType = options.featureType;
/**
* @protected
* @type {Object|string|undefined}
*/
this.featureNS = options.featureNS;
/**
* @protected
* @type {string|undefined}
*/
this.srsName = options.srsName;
/**
* @protected
* @type {string}
*/
this.schemaLocation = '';
/**
* @type {Object>}
*/
this.FEATURE_COLLECTION_PARSERS = {};
this.FEATURE_COLLECTION_PARSERS[this.namespace] = {
'featureMember': makeArrayPusher(this.readFeaturesInternal),
'featureMembers': makeReplacer(this.readFeaturesInternal),
};
this.supportedMediaTypes = ['application/gml+xml'];
}
/**
* @param {Element} node Node.
* @param {Array<*>} objectStack Object stack.
* @return {Array | undefined} Features.
*/
readFeaturesInternal(node, objectStack) {
const localName = node.localName;
let features = null;
if (localName == 'FeatureCollection') {
features = pushParseAndPop(
[],
this.FEATURE_COLLECTION_PARSERS,
node,
objectStack,
this,
);
} else if (
localName == 'featureMembers' ||
localName == 'featureMember' ||
localName == 'member'
) {
const context = objectStack[0];
let featureType = context['featureType'];
let featureNS = context['featureNS'];
const prefix = 'p';
const defaultPrefix = 'p0';
if (!featureType && node.childNodes) {
(featureType = []), (featureNS = {});
for (let i = 0, ii = node.childNodes.length; i < ii; ++i) {
const child = /** @type {Element} */ (node.childNodes[i]);
if (child.nodeType === 1) {
const ft = child.nodeName.split(':').pop();
if (!featureType.includes(ft)) {
let key = '';
let count = 0;
const uri = child.namespaceURI;
for (const candidate in featureNS) {
if (featureNS[candidate] === uri) {
key = candidate;
break;
}
++count;
}
if (!key) {
key = prefix + count;
featureNS[key] = uri;
}
featureType.push(key + ':' + ft);
}
}
}
if (localName != 'featureMember') {
// recheck featureType for each featureMember
context['featureType'] = featureType;
context['featureNS'] = featureNS;
}
}
if (typeof featureNS === 'string') {
const ns = featureNS;
featureNS = {};
featureNS[defaultPrefix] = ns;
}
/** @type {Object>} */
const parsersNS = {};
const featureTypes = Array.isArray(featureType)
? featureType
: [featureType];
for (const p in featureNS) {
/** @type {Object} */
const parsers = {};
for (let i = 0, ii = featureTypes.length; i < ii; ++i) {
const featurePrefix = featureTypes[i].includes(':')
? featureTypes[i].split(':')[0]
: defaultPrefix;
if (featurePrefix === p) {
parsers[featureTypes[i].split(':').pop()] =
localName == 'featureMembers'
? makeArrayPusher(this.readFeatureElement, this)
: makeReplacer(this.readFeatureElement, this);
}
}
parsersNS[featureNS[p]] = parsers;
}
if (localName == 'featureMember' || localName == 'member') {
features = pushParseAndPop(undefined, parsersNS, node, objectStack);
} else {
features = pushParseAndPop([], parsersNS, node, objectStack);
}
}
if (features === null) {
features = [];
}
return features;
}
/**
* @param {Element} node Node.
* @param {Array<*>} objectStack Object stack.
* @return {import("../geom/Geometry.js").default|import("../extent.js").Extent|undefined} Geometry.
*/
readGeometryOrExtent(node, objectStack) {
const context = /** @type {Object} */ (objectStack[0]);
context['srsName'] = node.firstElementChild.getAttribute('srsName');
context['srsDimension'] =
node.firstElementChild.getAttribute('srsDimension');
return pushParseAndPop(
null,
this.GEOMETRY_PARSERS,
node,
objectStack,
this,
);
}
/**
* @param {Element} node Node.
* @param {Array<*>} objectStack Object stack.
* @return {import("../extent.js").Extent|undefined} Geometry.
*/
readExtentElement(node, objectStack) {
const context = /** @type {Object} */ (objectStack[0]);
const extent = /** @type {import("../extent.js").Extent} */ (
this.readGeometryOrExtent(node, objectStack)
);
return extent ? transformExtentWithOptions(extent, context) : undefined;
}
/**
* @param {Element} node Node.
* @param {Array<*>} objectStack Object stack.
* @return {import("../geom/Geometry.js").default|undefined} Geometry.
*/
readGeometryElement(node, objectStack) {
const context = /** @type {Object} */ (objectStack[0]);
const geometry = /** @type {import("../geom/Geometry.js").default} */ (
this.readGeometryOrExtent(node, objectStack)
);
return geometry
? transformGeometryWithOptions(geometry, false, context)
: undefined;
}
/**
* @param {Element} node Node.
* @param {Array<*>} objectStack Object stack.
* @param {boolean} asFeature whether result should be wrapped as a feature.
* @return {Feature|Object} Feature
*/
readFeatureElementInternal(node, objectStack, asFeature) {
let geometryName;
const values = {};
for (let n = node.firstElementChild; n; n = n.nextElementSibling) {
let value;
const localName = n.localName;
// first, check if it is simple attribute
if (
n.childNodes.length === 0 ||
(n.childNodes.length === 1 &&
(n.firstChild.nodeType === 3 || n.firstChild.nodeType === 4))
) {
value = getAllTextContent(n, false);
if (ONLY_WHITESPACE_RE.test(value)) {
value = undefined;
}
} else {
if (asFeature) {
//if feature, try it as a geometry or extent
value =
localName === 'boundedBy'
? this.readExtentElement(n, objectStack)
: this.readGeometryElement(n, objectStack);
}
if (!value) {
//if not a geometry or not a feature, treat it as a complex attribute
value = this.readFeatureElementInternal(n, objectStack, false);
} else if (localName !== 'boundedBy') {
// boundedBy is an extent and must not be considered as a geometry
geometryName = localName;
}
}
const len = n.attributes.length;
if (len > 0 && !(value instanceof Geometry)) {
value = {_content_: value};
for (let i = 0; i < len; i++) {
const attName = n.attributes[i].name;
value[attName] = n.attributes[i].value;
}
}
if (values[localName]) {
if (!(values[localName] instanceof Array)) {
values[localName] = [values[localName]];
}
values[localName].push(value);
} else {
values[localName] = value;
}
}
if (!asFeature) {
return values;
}
const feature = new Feature(values);
if (geometryName) {
feature.setGeometryName(geometryName);
}
const fid =
node.getAttribute('fid') || getAttributeNS(node, this.namespace, 'id');
if (fid) {
feature.setId(fid);
}
return feature;
}
/**
* @param {Element} node Node.
* @param {Array<*>} objectStack Object stack.
* @return {Feature} Feature.
*/
readFeatureElement(node, objectStack) {
return this.readFeatureElementInternal(node, objectStack, true);
}
/**
* @param {Element} node Node.
* @param {Array<*>} objectStack Object stack.
* @return {Point|undefined} Point.
*/
readPoint(node, objectStack) {
const flatCoordinates = this.readFlatCoordinatesFromNode(node, objectStack);
if (flatCoordinates) {
return new Point(flatCoordinates, 'XYZ');
}
}
/**
* @param {Element} node Node.
* @param {Array<*>} objectStack Object stack.
* @return {MultiPoint|undefined} MultiPoint.
*/
readMultiPoint(node, objectStack) {
/** @type {Array>} */
const coordinates = pushParseAndPop(
[],
this.MULTIPOINT_PARSERS,
node,
objectStack,
this,
);
if (coordinates) {
return new MultiPoint(coordinates);
}
return undefined;
}
/**
* @param {Element} node Node.
* @param {Array<*>} objectStack Object stack.
* @return {MultiLineString|undefined} MultiLineString.
*/
readMultiLineString(node, objectStack) {
/** @type {Array} */
const lineStrings = pushParseAndPop(
[],
this.MULTILINESTRING_PARSERS,
node,
objectStack,
this,
);
if (lineStrings) {
return new MultiLineString(lineStrings);
}
}
/**
* @param {Element} node Node.
* @param {Array<*>} objectStack Object stack.
* @return {MultiPolygon|undefined} MultiPolygon.
*/
readMultiPolygon(node, objectStack) {
/** @type {Array} */
const polygons = pushParseAndPop(
[],
this.MULTIPOLYGON_PARSERS,
node,
objectStack,
this,
);
if (polygons) {
return new MultiPolygon(polygons);
}
}
/**
* @param {Element} node Node.
* @param {Array<*>} objectStack Object stack.
*/
pointMemberParser(node, objectStack) {
parseNode(this.POINTMEMBER_PARSERS, node, objectStack, this);
}
/**
* @param {Element} node Node.
* @param {Array<*>} objectStack Object stack.
*/
lineStringMemberParser(node, objectStack) {
parseNode(this.LINESTRINGMEMBER_PARSERS, node, objectStack, this);
}
/**
* @param {Element} node Node.
* @param {Array<*>} objectStack Object stack.
*/
polygonMemberParser(node, objectStack) {
parseNode(this.POLYGONMEMBER_PARSERS, node, objectStack, this);
}
/**
* @param {Element} node Node.
* @param {Array<*>} objectStack Object stack.
* @return {LineString|undefined} LineString.
*/
readLineString(node, objectStack) {
const flatCoordinates = this.readFlatCoordinatesFromNode(node, objectStack);
if (flatCoordinates) {
const lineString = new LineString(flatCoordinates, 'XYZ');
return lineString;
}
return undefined;
}
/**
* @param {Element} node Node.
* @param {Array<*>} objectStack Object stack.
* @return {Array|undefined} LinearRing flat coordinates.
*/
readFlatLinearRing(node, objectStack) {
const ring = pushParseAndPop(
null,
this.GEOMETRY_FLAT_COORDINATES_PARSERS,
node,
objectStack,
this,
);
if (ring) {
return ring;
}
return undefined;
}
/**
* @param {Element} node Node.
* @param {Array<*>} objectStack Object stack.
* @return {LinearRing|undefined} LinearRing.
*/
readLinearRing(node, objectStack) {
const flatCoordinates = this.readFlatCoordinatesFromNode(node, objectStack);
if (flatCoordinates) {
return new LinearRing(flatCoordinates, 'XYZ');
}
}
/**
* @param {Element} node Node.
* @param {Array<*>} objectStack Object stack.
* @return {Polygon|undefined} Polygon.
*/
readPolygon(node, objectStack) {
/** @type {Array>} */
const flatLinearRings = pushParseAndPop(
[null],
this.FLAT_LINEAR_RINGS_PARSERS,
node,
objectStack,
this,
);
if (flatLinearRings && flatLinearRings[0]) {
const flatCoordinates = flatLinearRings[0];
const ends = [flatCoordinates.length];
let i, ii;
for (i = 1, ii = flatLinearRings.length; i < ii; ++i) {
extend(flatCoordinates, flatLinearRings[i]);
ends.push(flatCoordinates.length);
}
return new Polygon(flatCoordinates, 'XYZ', ends);
}
return undefined;
}
/**
* @param {Element} node Node.
* @param {Array<*>} objectStack Object stack.
* @return {Array} Flat coordinates.
*/
readFlatCoordinatesFromNode(node, objectStack) {
return pushParseAndPop(
null,
this.GEOMETRY_FLAT_COORDINATES_PARSERS,
node,
objectStack,
this,
);
}
/**
* @param {Element} node Node.
* @param {import("./Feature.js").ReadOptions} [options] Options.
* @protected
* @return {import("../geom/Geometry.js").default} Geometry.
* @override
*/
readGeometryFromNode(node, options) {
const geometry = this.readGeometryElement(node, [
this.getReadOptions(node, options ? options : {}),
]);
return geometry ? geometry : null;
}
/**
* @param {Element} node Node.
* @param {import("./Feature.js").ReadOptions} [options] Options.
* @return {Array} Features.
* @override
*/
readFeaturesFromNode(node, options) {
const internalOptions = {
featureType: this.featureType,
featureNS: this.featureNS,
};
if (internalOptions) {
Object.assign(internalOptions, this.getReadOptions(node, options));
}
const features = this.readFeaturesInternal(node, [internalOptions]);
return features || [];
}
/**
* @param {Element} node Node.
* @return {import("../proj/Projection.js").default} Projection.
* @override
*/
readProjectionFromNode(node) {
return getProjection(
this.srsName
? this.srsName
: node.firstElementChild.getAttribute('srsName'),
);
}
}
GMLBase.prototype.namespace = GMLNS;
/**
* @const
* @type {Object>}
*/
GMLBase.prototype.FLAT_LINEAR_RINGS_PARSERS = {
'http://www.opengis.net/gml': {},
};
/**
* @const
* @type {Object>}
*/
GMLBase.prototype.GEOMETRY_FLAT_COORDINATES_PARSERS = {
'http://www.opengis.net/gml': {},
};
/**
* @const
* @type {Object>}
*/
GMLBase.prototype.GEOMETRY_PARSERS = {
'http://www.opengis.net/gml': {},
};
/**
* @const
* @type {Object>}
*/
GMLBase.prototype.MULTIPOINT_PARSERS = {
'http://www.opengis.net/gml': {
'pointMember': makeArrayPusher(GMLBase.prototype.pointMemberParser),
'pointMembers': makeArrayPusher(GMLBase.prototype.pointMemberParser),
},
};
/**
* @const
* @type {Object>}
*/
GMLBase.prototype.MULTILINESTRING_PARSERS = {
'http://www.opengis.net/gml': {
'lineStringMember': makeArrayPusher(
GMLBase.prototype.lineStringMemberParser,
),
'lineStringMembers': makeArrayPusher(
GMLBase.prototype.lineStringMemberParser,
),
},
};
/**
* @const
* @type {Object>}
*/
GMLBase.prototype.MULTIPOLYGON_PARSERS = {
'http://www.opengis.net/gml': {
'polygonMember': makeArrayPusher(GMLBase.prototype.polygonMemberParser),
'polygonMembers': makeArrayPusher(GMLBase.prototype.polygonMemberParser),
},
};
/**
* @const
* @type {Object>}
*/
GMLBase.prototype.POINTMEMBER_PARSERS = {
'http://www.opengis.net/gml': {
'Point': makeArrayPusher(GMLBase.prototype.readFlatCoordinatesFromNode),
},
};
/**
* @const
* @type {Object>}
*/
GMLBase.prototype.LINESTRINGMEMBER_PARSERS = {
'http://www.opengis.net/gml': {
'LineString': makeArrayPusher(GMLBase.prototype.readLineString),
},
};
/**
* @const
* @type {Object>}
*/
GMLBase.prototype.POLYGONMEMBER_PARSERS = {
'http://www.opengis.net/gml': {
'Polygon': makeArrayPusher(GMLBase.prototype.readPolygon),
},
};
/**
* @const
* @type {Object>}
*/
GMLBase.prototype.RING_PARSERS = {
'http://www.opengis.net/gml': {
'LinearRing': makeReplacer(GMLBase.prototype.readFlatLinearRing),
},
};
export default GMLBase;