@@ -42,8 +45,8 @@ var search = env.conf.templates && env.conf.templates.search
diff --git a/package.json b/package.json
index 39a7f2555..7585777bf 100644
--- a/package.json
+++ b/package.json
@@ -1,81 +1,104 @@
{
- "name": "@mapgis/webclient",
- "version": "10.5.0",
- "description": "",
- "main": "index.js",
- "scripts": {
- "website": "cd website && npm run serve",
- "leaflet-debug": "webpack --config src/config/opensource/leaflet-debug-config.js",
- "leaflet-release": "webpack --config src/config/opensource/leaflet-release-config.js --progress",
- "openlayers-debug": "webpack --config src/config/opensource/openlayers-debug-config.js",
- "openlayers-release": "webpack --config src/config/opensource/openlayers-release-config.js --progress",
- "mapbox-debug": "webpack --config src/config/opensource/mapbox-debug-config.js",
- "mapbox-release": "webpack --config src/config/opensource/mapbox-release-config.js --progress",
- "leaflet-plugin-debug": "webpack --config src/config/opensource/leaflet-plugin-debug-config.js",
- "leaflet-plugin-release": "webpack --config src/config/opensource/leaflet-plugin-release-config.js --progress",
- "openlayers-plugin-debug": "webpack --config src/config/opensource/openlayers-plugin-debug-config.js",
- "openlayers-plugin-release": "webpack --config src/config/opensource/openlayers-plugin-release-config.js --progress",
- "mapbox-plugin-debug": "webpack --config src/config/opensource/mapbox-plugin-debug-config.js",
- "mapbox-plugin-release": "webpack --config src/config/opensource/mapbox-plugin-release-config.js --progress",
- "cesium-plugin-debug": "webpack --config src/config/opensource/cesium-plugin-debug-config.js",
- "cesium-plugin-release": "webpack --config src/config/opensource/cesium-plugin-release-config.js --progress",
- "service-debug": "webpack --config src/config/opensource/service-debug-config.js",
- "service-release": "webpack --config src/config/opensource/service-release-config.js --progress",
- "build-docs-leaflet": "jsdoc -c ./docs/jsdoc-config/leaflet/docs.json -R ./docs/jsdoc-config/leaflet/index.md -r",
- "build-docs-mapboxgl": "jsdoc -c ./docs/jsdoc-config/mapboxgl/docs.json -R ./docs/jsdoc-config/mapboxgl/index.md -r",
- "build-docs-openlayers": "jsdoc -c ./docs/jsdoc-config/openlayers/docs.json -R ./docs/jsdoc-config/openlayers/index.md -r",
- "build-docs-cesium": "jsdoc -c ./docs/jsdoc-config/cesium/docs.json -R ./docs/jsdoc-config/cesium/index.md -r"
- },
- "author": "mapgis",
- "license": "Apache2",
- "devDependencies": {
- "babel-core": "^6.26.0",
- "babel-eslint": "^10.1.0",
- "babel-loader": "^7.1.2",
- "babel-plugin-import": "^1.13.0",
- "babel-plugin-transform-class-properties": "^6.11.5",
- "babel-plugin-transform-decorators-legacy": "^1.3.4",
- "babel-plugin-transform-flow-strip-types": "^6.21.0",
- "babel-plugin-transform-object-rest-spread": "^6.8.0",
- "babel-plugin-transform-runtime": "^6.15.0",
- "babel-preset-env": "^1.6.1",
- "babel-preset-es2015": "^6.24.1",
- "babel-runtime": "^6.11.6",
- "clean-webpack-plugin": "^0.1.18",
- "copy-webpack-plugin": "^4.5.1",
- "eslint": "^7.4.0",
- "eslint-config-airbnb": "^18.2.0",
- "eslint-config-prettier": "^6.11.0",
- "eslint-loader": "^1.9.0",
- "eslint-plugin-import": "^2.22.0",
- "eslint-plugin-jsx-a11y": "^6.3.1",
- "eslint-plugin-prettier": "^3.1.4",
- "eslint-plugin-react": "^7.20.3",
- "happypack": "^5.0.0",
- "json-loader": "^0.5.4",
- "prettier": "^2.0.5",
- "react-hot-loader": "^3.0.0-beta.6",
- "source-map-loader": "^0.2.3",
- "url-loader": "^0.5.9",
- "webpack": "4.19.1",
- "webpack-cleanup-plugin": "0.4.2",
- "webpack-cli": "2.1.5",
- "webpack-parallel-uglify-plugin": "0.4.2"
- },
- "dependencies": {
- "@mapbox/mapbox-gl-style-spec": "^13.15.0",
- "@mapgis/mapbox-gl": "^1.9.0",
- "axios": "^0.18.0",
- "cesium": "^1.70.1",
- "d3": "^5.16.0",
- "echarts": "^4.4.0",
- "fast-xml-parser": "^3.17.6",
- "html-webpack-plugin": "^4.3.0",
- "jsdoc": "^3.6.3",
- "mapv": "^2.0.40",
- "ol": "5.3.3",
- "proj4": "2.3.15",
- "qs": "^6.9.4",
- "webfont-matcher": "^1.1.0"
- }
+ "name": "@mapgis/webclient",
+ "version": "10.5.6",
+ "description": "",
+ "main": "index.js",
+ "scripts": {
+ "website": "cd website && npm run serve",
+ "website-build": "cd website && npm run build",
+ "build-all": "npm run build-all-plugins && npm run build-all-docs && npm run website-build",
+ "build-dist": "npm run build-all-plugins && npm run build-all-docs",
+ "build-all-plugins": "npm run leaflet-plugin-debug && npm run leaflet-plugin-release && npm run openlayers-plugin-debug && npm run openlayers-plugin-release && npm run mapbox-plugin-debug && npm run mapbox-plugin-release && npm run cesium-plugin-debug && npm run cesium-plugin-release && npm run service-debug && npm run service-release",
+ "build-all-docs": "npm run build-docs-leaflet && npm run build-docs-mapboxgl && npm run build-docs-openlayers && npm run build-docs-cesium",
+ "leaflet-debug": "webpack --config src/config/opensource/leaflet-debug-config.js",
+ "leaflet-release": "webpack --config src/config/opensource/leaflet-release-config.js --progress",
+ "openlayers-debug": "webpack --config src/config/opensource/openlayers-debug-config.js",
+ "openlayers-release": "webpack --config src/config/opensource/openlayers-release-config.js --progress",
+ "mapbox-debug": "webpack --config src/config/opensource/mapbox-debug-config.js",
+ "mapbox-release": "webpack --config src/config/opensource/mapbox-release-config.js --progress",
+ "leaflet-plugin-debug": "webpack --config src/config/opensource/leaflet-plugin-debug-config.js",
+ "leaflet-plugin-release": "webpack --config src/config/opensource/leaflet-plugin-release-config.js --progress",
+ "openlayers-plugin-debug": "webpack --config src/config/opensource/openlayers-plugin-debug-config.js",
+ "openlayers-plugin-release": "webpack --config src/config/opensource/openlayers-plugin-release-config.js --progress",
+ "mapbox-plugin-debug": "webpack --config src/config/opensource/mapbox-plugin-debug-config.js",
+ "mapbox-plugin-release": "webpack --config src/config/opensource/mapbox-plugin-release-config.js --progress",
+ "cesium-plugin-debug": "webpack --config src/config/opensource/cesium-plugin-debug-config.js",
+ "cesium-plugin-release": "webpack --config src/config/opensource/cesium-plugin-release-config.js --progress",
+ "service-debug": "webpack --config src/config/opensource/service-debug-config.js",
+ "service-release": "webpack --config src/config/opensource/service-release-config.js --progress",
+ "build-docs-service": "jsdoc -c ./docs/jsdoc-config/service/docs.json -R ./docs/jsdoc-config/service/index.md -r",
+ "build-docs-leaflet": "jsdoc -c ./docs/jsdoc-config/leaflet/docs.json -R ./docs/jsdoc-config/leaflet/index.md -r",
+ "build-docs-mapboxgl": "jsdoc -c ./docs/jsdoc-config/mapboxgl/docs.json -R ./docs/jsdoc-config/mapboxgl/index.md -r",
+ "build-docs-openlayers": "jsdoc -c ./docs/jsdoc-config/openlayers/docs.json -R ./docs/jsdoc-config/openlayers/index.md -r",
+ "build-docs-cesium": "jsdoc -c ./docs/jsdoc-config/cesium/docs.json -R ./docs/jsdoc-config/cesium/index.md -r",
+ "test-service": "node ./node_modules/karma/bin/karma start ./src/unittest/karma.conf.js",
+ "test-init": "node ./node_modules/karma/bin/karma init karma.conf.js"
+ },
+ "author": "mapgis",
+ "license": "Apache-2.0",
+ "devDependencies": {
+ "babel-core": "^6.26.3",
+ "babel-eslint": "^10.1.0",
+ "babel-loader": "^7.1.2",
+ "babel-plugin-import": "^1.13.0",
+ "babel-plugin-transform-class-properties": "^6.11.5",
+ "babel-plugin-transform-decorators-legacy": "^1.3.4",
+ "babel-plugin-transform-flow-strip-types": "^6.21.0",
+ "babel-plugin-transform-object-rest-spread": "^6.8.0",
+ "babel-plugin-transform-runtime": "^6.15.0",
+ "babel-preset-env": "^1.7.0",
+ "babel-preset-es2015": "^6.24.1",
+ "babel-runtime": "^6.11.6",
+ "babelify": "8",
+ "browserify": "^17.0.0",
+ "clean-webpack-plugin": "^0.1.18",
+ "copy-webpack-plugin": "^4.5.1",
+ "eslint": "^7.4.0",
+ "eslint-config-airbnb": "^18.2.0",
+ "eslint-config-prettier": "^6.11.0",
+ "eslint-loader": "^1.9.0",
+ "eslint-plugin-import": "^2.22.0",
+ "eslint-plugin-jsx-a11y": "^6.3.1",
+ "eslint-plugin-prettier": "^3.1.4",
+ "eslint-plugin-react": "^7.20.3",
+ "happypack": "^5.0.0",
+ "html-webpack-plugin": "^4.3.0",
+ "jasmine-core": "^3.7.1",
+ "json-loader": "^0.5.4",
+ "karma": "^6.3.2",
+ "karma-browserify": "^8.0.0",
+ "karma-chrome-launcher": "^3.1.0",
+ "karma-firefox-launcher": "^2.1.0",
+ "karma-jasmine": "^4.0.1",
+ "prettier": "^2.0.5",
+ "react-hot-loader": "^3.0.0-beta.6",
+ "source-map-loader": "^0.2.3",
+ "url-loader": "^0.5.9",
+ "watchify": "^4.0.0",
+ "webpack": "4.19.1",
+ "webpack-cleanup-plugin": "0.4.2",
+ "webpack-cli": "2.1.5",
+ "webpack-parallel-uglify-plugin": "0.4.2"
+ },
+ "dependencies": {
+ "@mapbox/leaflet-omnivore": "^0.3.4",
+ "@mapbox/mapbox-gl-style-spec": "^13.15.0",
+ "@mapgis/mapbox-gl": "^1.9.0",
+ "@turf/turf": "^6.3.0",
+ "axios": "^0.18.0",
+ "cesium": "1.84.0",
+ "d3": "^5.16.0",
+ "echarts": "^4.4.0",
+ "fast-xml-parser": "^3.17.6",
+ "fetch-ie8": "^1.5.0",
+ "jsdoc": "^3.6.3",
+ "leaflet": "^1.7.1",
+ "mapv": "^2.0.40",
+ "ol": "5.3.3",
+ "proj4": "2.3.15",
+ "promise-polyfill": "^8.2.3",
+ "qs": "^6.9.4",
+ "webfont-matcher": "^1.1.0",
+ "svg-pathdata": "^6.0.0"
+ }
}
diff --git a/src/cesiumjs/index.js b/src/cesiumjs/index.js
index 381d3b2b5..313ba64c1 100644
--- a/src/cesiumjs/index.js
+++ b/src/cesiumjs/index.js
@@ -1,4 +1,3 @@
-import { CesiumZondy } from './core/Base';
export { CesiumZondy } from './core/Base';
export * from './ui';
@@ -10,3 +9,448 @@ export * from './overlay';
export * from './provider';
export * from './render';
+
+import {
+ ServiceBase
+} from '../service/ServiceBase';
+
+import {
+ AnyLine,
+ Arc,
+ Zondy,
+ CAttStruct,
+ CAttDataRow,
+ CDisplayStyle,
+ CDisplayStyleExtend,
+ CDynNoteInfo,
+ CGDBInfo,
+ Circle,
+ CLineInfo,
+ CPointInfo,
+ CRegionInfo,
+ DynNoteLableType,
+ DynShowStyle,
+ XClsType,
+ VectClsType,
+ FeatureType,
+ FontShape,
+ LabelLinType,
+ LabelRegType,
+ LabelPntType,
+ RepeatType,
+ LabelSpreadType,
+ LineConstrain,
+ EightDirType,
+ ISShowArc,
+ NetAnalyType,
+ NetElemType,
+ CLinAdjustType,
+ CLinHeadType,
+ CLinJointType,
+ CLinStyleMakeType,
+ CItemType,
+ MapType,
+ LayerStatusType,
+ Feature,
+ FeatureGeometry,
+ FeatureGraphicBase,
+ FeatureSet,
+ GLine,
+ GPoint,
+ GRegion,
+ LabelLinInfo,
+ LabelRegInfo,
+ LablePntInfo,
+ MultiPolygon,
+ Point2D,
+ Polygon,
+ PolyLine,
+ Rectangle,
+ Tangram,
+ VectCls,
+ WebGraphicsInfo,
+ extend,
+ isArray,
+ extendDeep,
+ copy,
+ copyExcluce,
+ reset,
+ getElement,
+ isElement,
+ removeItem,
+ indexOf,
+ modifyDOMElement,
+ applyDefaults,
+ getParameterString,
+ getWFParameterString,
+ urlAppend,
+ getParameters,
+ IS_GECKO,
+ Browser,
+ getBrowser,
+ isSupportCanvas,
+ supportCanvas,
+ isInTheSameDomain,
+ toJSON,
+ transformResult,
+ copyAttributes,
+ copyAttributesWithClip,
+ cloneObject,
+ newGuid,
+ bind,
+ bindAsEventListener,
+ getTopAnalysisResult,
+ ChineseToUtf8,
+ DeepMerge,
+ merge,
+ mixin
+} from '../service/common';
+
+import {
+ ContourNoteParam,
+ ContourParam,
+ ContourZValue,
+ ContourRegionInfo,
+ MeshingParam,
+ NetAnalyse,
+ NetAnalysisExtent,
+ SlopLineParam
+} from '../service/Igserver/extend';
+
+import {
+ CommonServiceBase,
+ Events,
+ CORS,
+ RequestTimeout,
+ FetchRequest,
+ IgsServiceBase,
+ JSONFormat
+} from '../service/baseserver';
+
+import {
+ ColorInfo,
+ GDBInfo,
+ MapDoc,
+ CatalogService,
+ TileLayer,
+ VectorLayer
+} from '../service/Igserver/MRCS';
+
+import {
+ EditDocFeature,
+ EditLayerFeature,
+ EditServiceBase,
+ MultiGeoQuery,
+ MultiGeoQueryParameter,
+ ObjClsQuery,
+ ObjClsQueryParameter,
+ QueryByLayerParameter,
+ QueryDocFeature,
+ QueryFeatureRule,
+ QueryFeatureStruct,
+ QueryLayerFeature,
+ QueryParameter,
+ QueryParameterBase,
+ QueryServiceBase
+} from '../service/Igserver/MRFS';
+
+import {
+ AnalysisBase,
+ ClassBufferBase,
+ ClassBufferByMultiplyRing,
+ ClassBufferBySingleRing,
+ ClipBase,
+ ClipByCircle,
+ ClipByLayer,
+ ClipByPolygon,
+ ContourAnalyse,
+ FeatureBuffBase,
+ FeatureBuffByMultiplyRing,
+ FeatureBuffBySingleRing,
+ NetAnalysis,
+ OverlayBase,
+ OverlayByLayer,
+ OverlayByPolygon,
+ ProjectBase,
+ ProjectByLayer,
+ ProjectBySRID
+} from '../service/Igserver/MRFWS';
+
+import {
+ CalArea,
+ CalPolyLineLength,
+ CalServiceBase,
+ CProjectBySRSID,
+ CProjectParam,
+ GeometryAnalysisBase,
+ ProjectDots,
+ ProjectRang,
+ Smooth,
+ TopAnalysis
+} from '../service/Igserver/MRGS';
+
+import {
+ GetDocImageService,
+ GetLayerImageService,
+ GetMapImageService,
+ GetMapInfoService,
+ GetTileImageService,
+ MapServiceBase
+} from '../service/Igserver/MRMS';
+
+import {
+ CAllOtherDataItemInfoSource,
+ CAnnInfo,
+ CChartLabelFormat,
+ CChartTheme,
+ CChartThemeInfo,
+ CChartThemeRepresentInfo,
+ CChartType,
+ CDotDensityTheme,
+ CFourColorTheme,
+ CGraduatedSymbolTheme,
+ CLinInfo,
+ CMultiClassTheme,
+ CPntInfo,
+ CRandomTheme,
+ CRangeTheme,
+ CRangeThemeInfo,
+ CRegInfo,
+ CSimpleTheme,
+ CTheme,
+ CThemeInfo,
+ CUniqueTheme,
+ CUniqueThemeInfo,
+ ExpInfo,
+ FolderInfo,
+ FolderInfoAttribute,
+ ItemValue,
+ ThemeOper,
+ ThemesInfo
+} from '../service/Igserver/theme';
+
+import {
+ WMSCapabilities,
+ WMTSCapabilities,
+ OGCWMTSInfo,
+ OGCWMSInfo
+} from '../service/OGC';
+
+export {
+ ServiceBase
+};
+
+
+export {
+ AnyLine,
+ Arc,
+ Zondy,
+ CAttStruct,
+ CAttDataRow,
+ CDisplayStyle,
+ CDisplayStyleExtend,
+ CDynNoteInfo,
+ CGDBInfo,
+ Circle,
+ CLineInfo,
+ CPointInfo,
+ CRegionInfo,
+ DynNoteLableType,
+ DynShowStyle,
+ XClsType,
+ VectClsType,
+ FeatureType,
+ FontShape,
+ LabelLinType,
+ LabelRegType,
+ LabelPntType,
+ RepeatType,
+ LabelSpreadType,
+ LineConstrain,
+ EightDirType,
+ ISShowArc,
+ NetAnalyType,
+ NetElemType,
+ CLinAdjustType,
+ CLinHeadType,
+ CLinJointType,
+ CLinStyleMakeType,
+ CItemType,
+ MapType,
+ LayerStatusType,
+ Feature,
+ FeatureGeometry,
+ FeatureGraphicBase,
+ FeatureSet,
+ GLine,
+ GPoint,
+ GRegion,
+ LabelLinInfo,
+ LabelRegInfo,
+ LablePntInfo,
+ MultiPolygon,
+ Point2D,
+ Polygon,
+ PolyLine,
+ Rectangle,
+ Tangram,
+ VectCls,
+ WebGraphicsInfo,
+ extend,
+ isArray,
+ extendDeep,
+ copy,
+ copyExcluce,
+ reset,
+ getElement,
+ isElement,
+ removeItem,
+ indexOf,
+ modifyDOMElement,
+ applyDefaults,
+ getParameterString,
+ getWFParameterString,
+ urlAppend,
+ getParameters,
+ IS_GECKO,
+ Browser,
+ getBrowser,
+ isSupportCanvas,
+ supportCanvas,
+ isInTheSameDomain,
+ toJSON,
+ transformResult,
+ copyAttributes,
+ copyAttributesWithClip,
+ cloneObject,
+ newGuid,
+ bind,
+ bindAsEventListener,
+ getTopAnalysisResult,
+ ChineseToUtf8,
+ DeepMerge,
+ merge,
+ mixin
+};
+export {
+ ContourNoteParam,
+ ContourParam,
+ ContourZValue,
+ ContourRegionInfo,
+ MeshingParam,
+ NetAnalyse,
+ NetAnalysisExtent,
+ SlopLineParam
+};
+export {
+ CommonServiceBase,
+ Events,
+ CORS,
+ RequestTimeout,
+ FetchRequest,
+ IgsServiceBase,
+ JSONFormat
+};
+export {
+ ColorInfo,
+ GDBInfo,
+ MapDoc,
+ CatalogService,
+ TileLayer,
+ VectorLayer
+};
+export {
+ EditDocFeature,
+ EditLayerFeature,
+ EditServiceBase,
+ MultiGeoQuery,
+ MultiGeoQueryParameter,
+ ObjClsQuery,
+ ObjClsQueryParameter,
+ QueryByLayerParameter,
+ QueryDocFeature,
+ QueryFeatureRule,
+ QueryFeatureStruct,
+ QueryLayerFeature,
+ QueryParameter,
+ QueryParameterBase,
+ QueryServiceBase
+};
+export {
+ AnalysisBase,
+ ClassBufferBase,
+ ClassBufferByMultiplyRing,
+ ClassBufferBySingleRing,
+ ClipBase,
+ ClipByCircle,
+ ClipByLayer,
+ ClipByPolygon,
+ ContourAnalyse,
+ FeatureBuffBase,
+ FeatureBuffByMultiplyRing,
+ FeatureBuffBySingleRing,
+ NetAnalysis,
+ OverlayBase,
+ OverlayByLayer,
+ OverlayByPolygon,
+ ProjectBase,
+ ProjectByLayer,
+ ProjectBySRID
+};
+export {
+ CalArea,
+ CalPolyLineLength,
+ CalServiceBase,
+ CProjectBySRSID,
+ CProjectParam,
+ GeometryAnalysisBase,
+ ProjectDots,
+ ProjectRang,
+ Smooth,
+ TopAnalysis
+};
+export {
+ GetDocImageService,
+ GetLayerImageService,
+ GetMapImageService,
+ GetMapInfoService,
+ GetTileImageService,
+ MapServiceBase
+};
+export {
+ CAllOtherDataItemInfoSource,
+ CAnnInfo,
+ CChartLabelFormat,
+ CChartTheme,
+ CChartThemeInfo,
+ CChartThemeRepresentInfo,
+ CChartType,
+ CDotDensityTheme,
+ CFourColorTheme,
+ CGraduatedSymbolTheme,
+ CLinInfo,
+ CMultiClassTheme,
+ CPntInfo,
+ CRandomTheme,
+ CRangeTheme,
+ CRangeThemeInfo,
+ CRegInfo,
+ CSimpleTheme,
+ CTheme,
+ CThemeInfo,
+ CUniqueTheme,
+ CUniqueThemeInfo,
+ ExpInfo,
+ FolderInfo,
+ FolderInfoAttribute,
+ ItemValue,
+ ThemeOper,
+ ThemesInfo
+};
+
+export {
+ WMSCapabilities,
+ WMTSCapabilities,
+ OGCWMTSInfo,
+ OGCWMSInfo
+};
\ No newline at end of file
diff --git a/src/cesiumjs/layer/M3DLayer.js b/src/cesiumjs/layer/M3DLayer.js
index 554e790a9..64a88d5cc 100644
--- a/src/cesiumjs/layer/M3DLayer.js
+++ b/src/cesiumjs/layer/M3DLayer.js
@@ -73,26 +73,42 @@ export default class M3DLayer extends BaseLayer {
/**
* 添加m3d文档服务
* @function module:客户端数据服务.M3DLayer.prototype.append
- * @param {String} url 服务地址
- * @param {Object} optionsParam 包含以下参数
- * @param {Boolean} [optionsParam.autoReset = true] 是否自动定位
- * @param {Boolean} [optionsParam.synchronous = true] 是否异步请求
- * @param {Function} [optionsParam.loaded = function] 回调函数
- * @param {DefaultProxy} [optionsParam.proxy = defaultProxy] 代理
- * @param {Boolean} [optionsParam.showBoundingVolume = false] 是否显示包围盒
- * @param {Number} [optionsParam.maximumScreenSpaceError = 16] 用于控制模型显示细节 值较大将会渲染更少的贴图,进而可以提高性能,而较低的值将提高视觉质量
+ * @param {String} url 服务地址
+ * @param {Object} optionsParam 包含以下参数
+ * @param {Boolean} [optionsParam.autoReset = true] 是否自动定位
+ * @param {Boolean} [optionsParam.synchronous = true] 是否异步请求
+ * @param {Function} [optionsParam.loaded = function] 回调函数
+ * @param {Function} [options.getDocLayers = function] 回调函数,用于获取文档中的所有图层对象
+ * @param {DefaultProxy} [optionsParam.proxy = defaultProxy] 代理
+ * @param {Boolean} [optionsParam.showBoundingVolume = false] 是否显示包围盒
+ * @param {Number} [optionsParam.maximumScreenSpaceError = 16] 用于控制模型显示细节 值较大将会渲染更少的贴图,进而可以提高性能,而较低的值将提高视觉质量
+ * @param {String} [options.layers=undefined] 图层过滤功能
* @see {@link https://cesium.com/docs/cesiumjs-ref-doc/Cesium3DTileset.html}
* @returns {Array
} 返回m3d图层对象数组,长度为图层对象个数
* @example
+ *
* function callBackfunction(layer){
- * console.log(layer)
+ * console.log(layer)
* }
+ *
+ * // layers 属性类似二维服务
+ * // layers=show:0,1 表示只显示 layerIndex 为 0, 1 的图层
+ * // layers=hide:0,1 表示只隐藏 layerIndex 为 0, 1 的图层
+ *
* let result = m3d.append('http://develop.smaryun.com:6163/igs/rest/g3d/ModelM3D, {
- * autoReset:false,
- * synchronous:true,
- * showBoundingVolume:false,
- * maximumScreenSpaceError:16,
- * loaded:callBackfunction
+ * autoReset:false,
+ * synchronous:true,
+ * showBoundingVolume:false,
+ * maximumScreenSpaceError:16,
+ * layers:'layers=show:0',
+ * loaded:callBackfunction
+ * });
+ *
+ * m3d.append('http://develop.smaryun.com:6163/igs/rest/g3d/ModelM3D', {
+ * autoReset:false,
+ * synchronous:true,
+ * layers: 'layers=show:0',
+ * getDocLayers: function (docLayers) { docLayers[0].flyTo(viewer); }
* });
*
*/
@@ -107,6 +123,38 @@ export default class M3DLayer extends BaseLayer {
let proxy;
const docLayers = [];
+ const layersString = Cesium.defaultValue(options.layers, '');
+
+ let layersVec = layersString.split('=');
+
+ let layerShow = true;
+ let indexArray = [];
+
+ if (layersVec.length === 2) {
+ var tmpString = layersVec[1];
+ layersVec = tmpString.split(':');
+
+ if (layersVec.length === 2) {
+ if (layersVec[0] === 'show') {
+ layerShow = true;
+ } else if (layersVec[0] === 'hide') {
+ layerShow = false;
+ } else {
+ // eslint-disable-next-line no-console
+ console.log('layers 参数输入错误:' + layersString);
+ }
+
+ tmpString = layersVec[1];
+ layersVec = tmpString.split(',');
+
+ if (layersVec.length > 0) {
+ for (var index = 0; index < layersVec.length; index++) {
+ indexArray.push(parseInt(layersVec[index]));
+ }
+ }
+ }
+ }
+
if (Cesium.defined(options)) {
if (Cesium.defined(options.proxy)) {
// 不放在defaultValue中 new 会影响性能
@@ -116,6 +164,10 @@ export default class M3DLayer extends BaseLayer {
synchronous = Cesium.defaultValue(options.synchronous, true);
}
+ const docReadyPromise = new Cesium.when.defer();
+
+ docReadyPromise.resolve(docLayers);
+
const _callBack = (params) => {
const _params = params;
if (Cesium.defined(options.loaded) && typeof options.loaded === 'function') {
@@ -123,18 +175,38 @@ export default class M3DLayer extends BaseLayer {
}
};
+ const _callBack2 = (params) => {
+ const _params = params;
+ if (Cesium.defined(options.getDocLayers) && typeof options.getDocLayers === 'function') {
+ options.getDocLayers(_params);
+ }
+ };
+
const parseDocInfo = (info) => {
if (info !== undefined && info.sceneInfos.length > 0) {
const { layers } = info.sceneInfos[0];
- layers.forEach((layer) => {
+ for (let i = 0; i < layers.length; i++) {
+ const layer = layers[i];
const type = parseInt(layer.layerType, 10);
- if (type === LayerType.M3DLAYER) {
+ if (type === LayerType.M3DLAYER || type === LayerType.MODELLAYER) {
const { layerRenderIndex, layerIndex, gdbpUrl, isVisible } = layer;
- const m3d = this.appendM3DLayer(baseUrl, layerRenderIndex, layerIndex, gdbpUrl, isVisible, true, options);
+
+ let isShow = true;
+
+ if (layersString.length > 7 && indexArray.length > 0) {
+ isShow = (indexArray.indexOf(layerIndex) !== -1) === layerShow;
+ } else {
+ isShow = isVisible;
+ }
+
+ const m3d = this.appendM3DLayer(baseUrl, layerRenderIndex, layerIndex, gdbpUrl, isShow, true, options);
docLayers.push(m3d);
m3d.readyPromise.then(_callBack);
}
- });
+ }
+ if (docReadyPromise !== undefined) {
+ docReadyPromise.then(_callBack2);
+ }
}
};
@@ -268,7 +340,11 @@ export default class M3DLayer extends BaseLayer {
let hpr = new Cesium.Matrix3();
const hprObj = new Cesium.HeadingPitchRoll(Cesium.Math.PI, Cesium.Math.PI, Cesium.Math.PI);
hpr = Cesium.Matrix3.fromHeadingPitchRoll(hprObj, hpr);
- const modelMatrix = Cesium.Matrix4.multiplyByTranslation(Cesium.Transforms.eastNorthUpToFixedFrame(Cesium.Cartesian3.fromDegrees(longtitude, latitude, 0)), new Cesium.Cartesian3(), new Cesium.Matrix4());
+ const modelMatrix = Cesium.Matrix4.multiplyByTranslation(
+ Cesium.Transforms.eastNorthUpToFixedFrame(Cesium.Cartesian3.fromDegrees(longtitude, latitude, 0)),
+ new Cesium.Cartesian3(),
+ new Cesium.Matrix4()
+ );
Cesium.Matrix4.multiplyByMatrix3(modelMatrix, hpr, modelMatrix);
const { _root } = tileSet;
_root.transform = modelMatrix;
diff --git a/src/cesiumjs/layer/TerrainLayer.js b/src/cesiumjs/layer/TerrainLayer.js
index 3efadf028..6e320b190 100644
--- a/src/cesiumjs/layer/TerrainLayer.js
+++ b/src/cesiumjs/layer/TerrainLayer.js
@@ -28,6 +28,7 @@ export default class TerrainLayer extends BaseLayer {
* @param {Boolean} [optionsParam.synchronous = true] 是否异步请求
* @param {Number} [optionsParam.scale = 1] 地形缩放比例
* @param {Object} [optionsParam.range] 地形范围
+ * @param {Boolean} [optionsParam.requestVertexNormals = false] 是否请求法向
* @returns {Object} 地形图层对象
* @example
* appendTerrainLayer(baseUrl, sceneIndex, layerIndex, {
@@ -41,12 +42,16 @@ export default class TerrainLayer extends BaseLayer {
if (Cesium.defined(proxy)) {
_proxy = new Cesium.DefaultProxy(proxy);
}
- const dataUrl = `${baseUrl}/GetTerrain?sceneIndex=${sceneIndex}&layerIndex=${layerIndex}&Level={z}&Row={x}&Col={y}&xdensity=65&ydensity=65&webGL=true`;
+ let requestVertexNormals = Cesium.defaultValue(options.requestVertexNormals, false);
+ const dataUrl = `${baseUrl}/GetTerrain?sceneIndex=${sceneIndex}&layerIndex=${layerIndex}&Level={z}&Row={x}&Col={y}&xdensity=65&ydensity=65&webGL=true&hasNormals=${requestVertexNormals}`;
const terrainProvider = new Cesium.MapGISTerrainProvider({
url: dataUrl,
range: options.range,
proxy: _proxy,
- scale: options.scale
+ scale: options.scale,
+ requestVertexNormals: requestVertexNormals,
+ terrainColorTblInfo:options.terrainColorTblInfo,
+ range3D: options.range3D
});
this.viewer.terrainProvider = terrainProvider;
@@ -60,10 +65,17 @@ export default class TerrainLayer extends BaseLayer {
* @param {Object} optionsParam 包含以下参数
* @param {Boolean} [optionsParam.synchronous = true] 是否异步请求
* @param {DefaultProxy} [optionsParam.proxy = defaultProxy] 代理
+ * @param {Function} [optionsParam.loaded = function] 加载成功回调函数
+ * @param {Function} [optionsParam.getDocLayers = function] 回调获取图层对象
+ * @param {Boolean} [optionsParam.requestVertexNormals = false] 是否请求法向
* @returns 地形层对象
* @example
* let terrain = new TerrainLayer(viewer:viewer);
- * let terrainProivder = terrain.append('http://develop.smaryun.com:6163/igs/rest/g3d/terrain');
+ * let terrainProivder = terrain.append('http://develop.smaryun.com:6163/igs/rest/g3d/terrain'{
+ * requestVertexNormals:false,
+ * loaded:callBackfunction,
+ * getDocLayers:function (docLayers){}
+ * });
*/
append(url, optionsParam) {
if (!Cesium.defined(url)) {
@@ -75,7 +87,8 @@ export default class TerrainLayer extends BaseLayer {
let resource;
let proxy;
const docLayers = [];
-
+ let docReadyPromise = new Cesium.when.defer();
+ docReadyPromise.resolve(docLayers);
if (Cesium.defined(options)) {
if (Cesium.defined(options.proxy)) {
// 不放在defaultValue中 new 会影响性能
@@ -84,25 +97,49 @@ export default class TerrainLayer extends BaseLayer {
Cesium.defaultValue(options.proxy, undefined);
synchronous = Cesium.defaultValue(options.synchronous, true);
}
-
+ const _callBack = (params) => {
+ const _params = params;
+ if (Cesium.defined(options.loaded) && typeof options.loaded === 'function') {
+ options.loaded(_params);
+ }
+ };
+ const _callBack2 = (params) => {
+ const _params = params;
+ if (Cesium.defined(options.getDocLayers) && typeof options.getDocLayers === 'function') {
+ options.getDocLayers(_params);
+ }
+ };
const parseDocInfo = (info) => {
if (info !== undefined && info.sceneInfos.length > 0) {
const { layers } = info.sceneInfos[0];
layers.forEach((layer) => {
- const { layerType, layerRenderIndex, elevationScale, range } = layer;
+ const { layerType, layerRenderIndex, range, range3D , terrainLayer } = layer;
+ const { terrainColorTblInfo, elevationScale } = terrainLayer;
const type = parseInt(layerType, 10);
if (type === LayerType.TERRAINLAYER) {
const sceneIndex = 0;
const opt = {
range,
+ range3D,
+ terrainColorTblInfo,
scale: elevationScale
};
Object.extend(options, opt);
+ // if(Cesium.defined(terrainColorTblInfo)){
+ // options.terrainColorTblInfo=terrainColorTblInfo;
+ // }
+ // if(Cesium.defined(range3D)){
+ // options.range3D=range3D;
+ // }
const layerRes = this.appendTerrainLayer(baseUrl, sceneIndex, layerRenderIndex, proxy, options);
docLayers.push(layerRes);
+ layerRes.readyPromise.then(_callBack);
}
});
}
+ if(Cesium.defined(docReadyPromise)) {
+ docReadyPromise.then(_callBack2(docLayers));
+ }
};
if (synchronous) {
@@ -152,6 +189,20 @@ export default class TerrainLayer extends BaseLayer {
});
this.viewer.terrainProvider = terrainProviderMeshes;
}
+ /**
+ * 删除地形图层
+ * @function module:客户端数据服务.TerrainLayer.prototype.deleteTerrain
+ * @example
+ * let terrain = new TerrainLayer(viewer:viewer);
+ * let terrainProivder = terrain.append('http://develop.smaryun.com:6163/igs/rest/g3d/terrain');
+ * terrain.deleteTerrain();
+ */
+ deleteTerrain() {
+ if(Cesium.defined(this.viewer.terrainProvider)){
+ this.viewer.terrainProvider = null;
+ }
+ this.viewer.terrainProvider = new Cesium.EllipsoidTerrainProvider();
+ }
}
CesiumZondy.Layer.TerrainLayer = TerrainLayer;
diff --git a/src/cesiumjs/layer/ThirdPartyLayer.js b/src/cesiumjs/layer/ThirdPartyLayer.js
index 447e9a018..d7d4d34dc 100644
--- a/src/cesiumjs/layer/ThirdPartyLayer.js
+++ b/src/cesiumjs/layer/ThirdPartyLayer.js
@@ -27,12 +27,12 @@ export default class ThirdPartyLayer extends BaseLayer {
const options = Cesium.defaultValue(optionsParam, {});
this._isHistoryImage = Cesium.defaultValue(options.isHistoryImage, false);
this._imageVersion = Cesium.defaultValue(options.imageVersion, '0');
- const offset = Cesium.defaultValue(options.Offset, false);
- let offsetLabel = '';
- if (offset) {
- offsetLabel = '&gl=cn';
- }
- let url = `http://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png`;
+ // const offset = Cesium.defaultValue(options.Offset, false);
+ // let offsetLabel = '';
+ // if (offset) {
+ // offsetLabel = '&gl=cn';
+ // }
+ const url = `http://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png`;
const osmMap = this.viewer.imageryLayers.addImageryProvider(
new Cesium.UrlTemplateImageryProvider({
url,
@@ -140,7 +140,7 @@ export default class ThirdPartyLayer extends BaseLayer {
* let tilelayer = thirdLayer.appendBaiduMap({ptype:'sate'});
*/
appendBaiduMap(optionsParam) {
- const baiduProvider = this.viewer.imageryLayers.addImageryProvider(new Cesium.BaiduMapProvider(optionsParam));
+ const baiduProvider = this.viewer.imageryLayers.addImageryProvider(new Cesium.BaiduMapImagerProvider(optionsParam));
return baiduProvider;
}
@@ -201,7 +201,7 @@ export default class ThirdPartyLayer extends BaseLayer {
if (!Cesium.defined(options.token)) {
Cesium.deprecationWarning('http://www.tianditu.gov.cn', '请到天地图官网自行申请开发token,自带token仅做功能验证随时可能失效');
}
- const url = 'http://t0.tianditu.com/DataServer?';
+ const url = 'http://t0.tianditu.gov.cn/DataServer?';
const row = '_c&X={x}&Y={y}&L={l}';
switch (options.ptype) {
case 'vec':
@@ -253,7 +253,7 @@ export default class ThirdPartyLayer extends BaseLayer {
const options = Cesium.defaultValue(optionsParam, {});
const token = Cesium.defaultValue(options.token, '9c157e9585486c02edf817d2ecbc7752');
if (Cesium.defined(options.ptype)) {
- let url = `http://{s}.tianditu.com/{lw}/wmts?service=WMTS&version=1.0.0&request=GetTile&tilematrix={TileMatrix}&layer={layerType}&style={style}&tilerow={TileRow}&tilecol={TileCol}&tilematrixset=w&format=tiles&tk=${token}`;
+ let url = `http://{s}.tianditu.gov.cn/{lw}/wmts?service=WMTS&version=1.0.0&request=GetTile&tilematrix={TileMatrix}&layer={layerType}&style={style}&tilerow={TileRow}&tilecol={TileCol}&tilematrixset=w&format=tiles&tk=${token}`;
switch (options.ptype) {
case 'img':
url = url.replace('{lw}', 'img_w');
diff --git a/src/cesiumjs/layer/TilesLayer.js b/src/cesiumjs/layer/TilesLayer.js
index d3450c310..887750505 100644
--- a/src/cesiumjs/layer/TilesLayer.js
+++ b/src/cesiumjs/layer/TilesLayer.js
@@ -73,6 +73,9 @@ export default class TilesLayer extends BaseLayer {
* @param {Number} [optionsParam.rowNum=1] 瓦片初始级的列数 默认为1
* @param {Number} [optionsParam.maxLevel=19] 瓦片最大显示级数 默认为19
* @param {String} [optionsParam.proxy] 转发代理
+ * @param {Array} [options.gdbps] gdbps地址数组
+ * @param {String} [options.layers] layers参数,用于过滤图层
+ *
* @returns {ImageryLayer} 瓦片对象
* @example
* 如果裁瓦片的时候是按照经纬度裁剪的瓦片则只设置最大级数即可
@@ -91,6 +94,12 @@ export default class TilesLayer extends BaseLayer {
append2DDocTile(url, optionsParam) {
// 中地新版正常二维瓦片
const options = Cesium.defaultValue(optionsParam, {});
+
+ if (Cesium.defined(options.gdbps) && Cesium.defined(options.layers)) {
+ // eslint-disable-next-line no-console
+ console.log('不能同时定义 gdbps 和 layers');
+ }
+
options.url = url;
const mapGis2DDocTile = this.viewer.imageryLayers.addImageryProvider(new Cesium.MapGIS2DDocMapProvider(options));
return mapGis2DDocTile;
diff --git a/src/cesiumjs/manager/AdvancedAnalysisManager.js b/src/cesiumjs/manager/AdvancedAnalysisManager.js
index 2905b3b75..5442a4f9a 100644
--- a/src/cesiumjs/manager/AdvancedAnalysisManager.js
+++ b/src/cesiumjs/manager/AdvancedAnalysisManager.js
@@ -376,7 +376,7 @@ export default class AdvancedAnalysisManager {
createRain(options) {
const optionsParam = Cesium.defaultValue(options, {});
const collection = this.viewer.scene.postProcessStages;
- const rain = Cesium.PostProcessStageLibrary.createRainStage();
+ const rain = Cesium.PostProcessStageLibrary.createRainStage(optionsParam);
collection.add(rain);
this.scene.skyAtmosphere.hueShift = Cesium.defaultValue(optionsParam.hueShift, -0.8);
this.scene.skyAtmosphere.saturationShift = Cesium.defaultValue(optionsParam.saturationShift, -0.7);
diff --git a/src/cesiumjs/manager/AnalysisManager.js b/src/cesiumjs/manager/AnalysisManager.js
index 2a1b61e54..e3a9c36d5 100644
--- a/src/cesiumjs/manager/AnalysisManager.js
+++ b/src/cesiumjs/manager/AnalysisManager.js
@@ -556,14 +556,20 @@ export default class AnalysisManager {
* @param {Object} tileset 图层集
* @param {Array} planes 平面集
* @param {Object} options 动态剖切参数
- * @param {Color} [options.color] 材质
+ * @param {Color} [options.color=Color.WHITE.withAlpha(0.5)] 材质
+ * @param {Number} [options.scaleHeight=2.5] 高度缩放比
+ * @param {Number} [options.scaleWidth=2.5] 宽度缩放比
* @param {Boolean} [options.interaction] 交互
+ *
+ * @returns {Object} 返回对象
*/
createDynamicCutting(tilesets, planes, options) {
if (!Cesium.defined(tilesets) && tilesets.length > 0) {
return undefined;
}
- let material = Cesium.Color.WHITE.withAlpha(0.02);
+ var scaleHeight = Cesium.defaultValue(options.scaleHeight, 2.5);
+ var scaleWidth = Cesium.defaultValue(options.scaleWidth, 2.5);
+ let material = Cesium.Color.WHITE.withAlpha(0.5);
let interaction = false;
const optionsParam = Cesium.defaultValue(options, {});
@@ -585,10 +591,11 @@ export default class AnalysisManager {
const center = new Cesium.Cartesian3();
Cesium.Matrix4.getTranslation(transform, center);
for (let i = 0; i < planes.length; i += 1) {
+ const normal = planes[i].normal._cartesian3;
const planeEntity = this.viewer.entities.add({
- position: center,
+ position: Cesium.CommonFunction.getPointOntoPlane(center, normal, tileset.boundingSphere.center, new Cesium.Cartesian3),
plane: {
- dimensions: new Cesium.Cartesian2(radius * 250, radius * 2.5),
+ dimensions: new Cesium.Cartesian2(radius * scaleWidth, radius * scaleHeight),
material
}
});
diff --git a/src/cesiumjs/manager/LabelLayer.js b/src/cesiumjs/manager/LabelLayer.js
index 477260830..7476b0514 100644
--- a/src/cesiumjs/manager/LabelLayer.js
+++ b/src/cesiumjs/manager/LabelLayer.js
@@ -205,6 +205,137 @@ export default class LabelLayer extends BaseLayer {
return labelIcon;
}
+ /**
+ * 添加图标注记
+ * @function module:客户端可视化.LabelLayer.prototype.appendLabelIconEx
+ * @param {Number} lon 经度
+ * @param {Number} lat 纬度
+ * @param {Number} height 高程
+ * @param {Object} [options] 可配置参数
+ * @param {String} [options.iconUrl] 图标路径,默认值: undefined
+ * @param {String} [options.text] 注记文字内容,默认值: undefined
+ * @param {Number} [options.disableDepthTestDistance] 图片和文字注记的深度测试
+ * @param {NearFarScalar} [options.translucencyByDistance] 透明显示参数 默认值: new NearFarScalar(1.5e5, 1.0, 1.5e9, 0.0)
+ * @param {NearFarScalar} [options.scaleByDistance] 缩放距离参数 默认值: new Cesium.NearFarScalar(1.5e2, 1.5, 1.5e7, 0.0)
+ * @param {Number} [options.iconWidth] 图标宽度 默认值: 64
+ * @param {Number} [options.iconHeight] 图标高度 默认值: 64
+ * @param {Cartesian2} [options.icoPixelOffset] 图标偏移 默认值: Cartesian2.ZERO
+ * @param {NearFarScalar} [options.icoPixelOffsetScaleByDistance] 图标偏移值缩放距离参数 默认值: undefined
+ * @param {Number} [options.icoVerticalOrigin] 图标相对于原点的竖直位置 默认值: VerticalOrigin.CENTER
+ * @param {Number} [options.icoHorizontalOrigin] 图标相对于原点的水平位置 默认值: HorizontalOrigin.TOP
+ * @param {String} [options.font] 字体 这里将字体和大小放在一起 eg:'14pt 楷体'
+ * @param {Cartesian2} [options.labelPixelOffset] 默认值: new Cartesian2(0.0, -iconHeight / 4)
+ * @param {NearFarScalar} [options.labelPixelOffsetScaleByDistance] 文字注记偏移值缩放距离参数默认值: new NearFarScalar(1.5e5, 1.5, 1.5e7, 0.0)
+ * @param {Color} [options.labelFillColor] 默认值: Color.WHITE
+ * @param {Color} [options.labelBackgroundColor] 默认值: new Color(0.165, 0.165, 0.165, 0.8)
+ * @param {Bool} [options.labelShow] 默认值: true
+ * @param {BOOL} [options.labelShowBackground] 默认值: false
+ * @param {Number} [options.labelStyle] 默认值: LabelStyle.FILL_AND_OUTLINE
+ * @param {Color} [options.labelOutlineWidth] 默认值: 1
+ * @param {String} [options.labelVerticalOrigin] 文字注记相对于原点的竖直位置 默认值: VerticalOrigin.BOTTOM
+ * @param {String} [options.labelHorizontalOrigin] 文字注记相对于原点的水平位置 默认值: HorizontalOrigin.BOTTOM
+ * @param {String} [options.attribute] 属性参数 默认值: undefined
+ * @example
+ * let labelLayer = new LabelLayer({viewer:viewer});
+ * const options = { iconUrl: '/car.png', text: '注记文本', font: '14pt 楷体', labelShowBackground: true, attribute: '这是属性信息查询时可以看到' }
+ * const labelIcon = labelLayer.appendLabelIconEx(110, 33, 0, options);
+ * @returns {Entity} labelIcon 图标注记对象 移除通过removeEntity(entity)
+ */
+ appendLabelIconEx(lon, lat, height, options) {
+ // eslint-disable-next-line no-param-reassign
+ options = Cesium.defaultValue(options, {});
+
+ const text = Cesium.defaultValue(options.text, undefined);
+ const iconUrl = Cesium.defaultValue(options.iconUrl, undefined);
+
+ if (!Cesium.defined(text) && !Cesium.defined(iconUrl)) {
+ // eslint-disable-next-line no-console
+ console.log('text 和 iconUrl 都未定义,无法正常添加 labelIcon');
+ return null;
+ }
+
+ const translucencyByDistance = Cesium.defaultValue(options.translucencyByDistance, new Cesium.NearFarScalar(1.5e5, 1.0, 1.5e9, 0.0));
+ const scaleByDistance = Cesium.defaultValue(options.scaleByDistance, new Cesium.NearFarScalar(1.5e2, 1.5, 1.5e7, 0.0));
+ const disableDepthTestDistance = Cesium.defaultValue(options.disableDepthTestDistance, Number.POSITIVE_INFINITY);
+
+ const iconWidth = Cesium.defaultValue(options.iconWidth, 64);
+ const iconHeight = Cesium.defaultValue(options.iconHeight, 64);
+ const icoPixelOffset = Cesium.defaultValue(options.icoPixelOffset, new Cesium.Cartesian2(0.0, 0.0));
+ const icoPixelOffsetScaleByDistance = Cesium.defaultValue(options.icoPixelOffsetScaleByDistance, undefined);
+ const icoVerticalOrigin = Cesium.defaultValue(options.icoVerticalOrigin, Cesium.VerticalOrigin.CENTER);
+ const icoHorizontalOrigin = Cesium.defaultValue(options.icoHorizontalOrigin, Cesium.HorizontalOrigin.TOP);
+
+ const font = Cesium.defaultValue(options.font, '14pt 楷体');
+ const labelPixelOffset = Cesium.defaultValue(options.labelPixelOffset, new Cesium.Cartesian2(0.0, -iconHeight / 2));
+ const labelPixelOffsetScaleByDistance = Cesium.defaultValue(
+ options.labelPixelOffsetScaleByDistance,
+ new Cesium.NearFarScalar(1.5e5, 1.5, 1.5e7, 0.0)
+ );
+ const labelFillColor = Cesium.defaultValue(options.labelFillColor, Cesium.Color.WHITE);
+ const labelShowBackground = Cesium.defaultValue(options.labelShowBackground, false);
+ const labelBackgroundColor = Cesium.defaultValue(options.labelBackgroundColor, new Cesium.Color(0.165, 0.165, 0.165, 0.8));
+ const labelShow = Cesium.defaultValue(options.labelShow, true);
+ const labelStyle = Cesium.defaultValue(options.labelStyle, Cesium.LabelStyle.FILL_AND_OUTLINE);
+ const labelOutlineWidth = Cesium.defaultValue(options.labelOutlineWidth, 1);
+ const labelVerticalOrigin = Cesium.defaultValue(options.labelVerticalOrigin, Cesium.VerticalOrigin.BOTTOM);
+ const labelHorizontalOrigin = Cesium.defaultValue(options.labelHorizontalOrigin, Cesium.HorizontalOrigin.BOTTOM);
+
+ const attribute = Cesium.defaultValue(options.attribute, undefined);
+
+ const entity = {
+ name: Cesium.defined(text) ? text : 'defaut',
+ position: Cesium.Cartesian3.fromDegrees(lon, lat, height),
+
+ description: attribute
+ };
+
+ if (Cesium.defined(iconUrl)) {
+ entity.billboard = {
+ // 图标
+ image: iconUrl,
+ width: iconWidth,
+ height: iconHeight,
+ pixelOffset: icoPixelOffset,
+ pixelOffsetScaleByDistance: icoPixelOffsetScaleByDistance,
+ // 随远近隐藏
+ translucencyByDistance,
+ // 随远近缩放
+ scaleByDistance,
+ // 定位点
+ verticalOrigin: icoVerticalOrigin,
+ horizontalOrigin: icoHorizontalOrigin,
+ disableDepthTestDistance
+ };
+ }
+
+ if (Cesium.defined(text)) {
+ entity.label = {
+ // 文字标签
+ text,
+ font,
+ show: labelShow,
+ style: labelStyle,
+ fillColor: labelFillColor,
+ showBackground: labelShowBackground,
+ backgroundColor: labelBackgroundColor,
+ outlineWidth: labelOutlineWidth,
+ verticalOrigin: labelVerticalOrigin, // 垂直方向以底部来计算标签的位置
+ horizontalOrigin: labelHorizontalOrigin, // 原点在下方
+ // heightReference: heightReference,
+ pixelOffset: labelPixelOffset, // x,y方向偏移 相对于屏幕
+ pixelOffsetScaleByDistance: labelPixelOffsetScaleByDistance,
+ // 随远近缩放
+ scaleByDistance,
+ // 随远近隐藏
+ translucencyByDistance,
+ disableDepthTestDistance
+ };
+ }
+
+ const labelIcon = this.viewer.entities.add(entity);
+ return labelIcon;
+ }
+
/**
* 添加图标注记
* @function module:客户端可视化.LabelLayer.prototype.appendLabelIconComm
diff --git a/src/cesiumjs/manager/PopupController.js b/src/cesiumjs/manager/PopupController.js
index 560b387a9..2a8fce91f 100644
--- a/src/cesiumjs/manager/PopupController.js
+++ b/src/cesiumjs/manager/PopupController.js
@@ -86,10 +86,10 @@ export default class PopupController extends BaseLayer {
const randID = CommonFuncManager.generateRandom();
const rootContentDiv = document.createElement('div');
rootContentDiv.setAttribute('id', `popup_${randID}`);
- rootContentDiv.setAttribute('class', 'cesium-popup');
+ rootContentDiv.setAttribute('class', 'mapgis-popup');
rootContentDiv.setAttribute('style', 'top:5px;left:0;');
const closeDiv = document.createElement('a');
- closeDiv.setAttribute('class', 'cesium-popup-close-button');
+ closeDiv.setAttribute('class', 'mapgis-popup-close-button');
// closeDiv.setAttribute('href', '#');
closeDiv.innerHTML = '×';
const webControl = this;
@@ -101,18 +101,18 @@ export default class PopupController extends BaseLayer {
rootContentDiv.appendChild(closeDiv);
const contentDiv = document.createElement('div');
- contentDiv.setAttribute('class', 'cesium-popup-content-wrapper');
+ contentDiv.setAttribute('class', 'mapgis-popup-content-wrapper');
const contentLinkDiv = document.createElement('div');
- contentLinkDiv.setAttribute('class', 'cesium-popup-content');
+ contentLinkDiv.setAttribute('class', 'mapgis-popup-content');
contentLinkDiv.setAttribute('style', 'max-width: 300px;');
contentLinkDiv.innerHTML = content;
contentDiv.appendChild(contentLinkDiv);
rootContentDiv.appendChild(contentDiv);
const tipContainDiv = document.createElement('div');
- tipContainDiv.setAttribute('class', 'cesium-popup-tip-container');
+ tipContainDiv.setAttribute('class', 'mapgis-popup-tip-container');
const tipDiv = document.createElement('div');
- tipDiv.setAttribute('class', 'cesium-popup-tip');
+ tipDiv.setAttribute('class', 'mapgis-popup-tip');
tipContainDiv.appendChild(tipDiv);
rootContentDiv.appendChild(tipContainDiv);
@@ -274,7 +274,7 @@ export default class PopupController extends BaseLayer {
if (removeDiv && popDiv.parentNode !== null) {
popDiv.parentNode.removeChild(popDiv);
}
- if (owner.popupContain !== null && owner.popupContain.length > 0) {
+ if (Cesium.defined(owner.popupContain) && owner.popupContain.length > 0) {
for (let i = 0, n = 0; i < owner.popupContain.length; i += 1) {
if (owner.popupContain[i].id !== popID) {
owner.popupContain[(n += 1)] = owner.popupContain[i];
@@ -282,7 +282,7 @@ export default class PopupController extends BaseLayer {
}
owner.popupContain.length -= 1;
}
- if (owner.popupContain.length <= 0) {
+ if (Cesium.defined(owner.popupContain) && owner.popupContain.length <= 0) {
owner.viewer.camera.percentageChanged = 0.5;
owner.viewer.camera.changed.removeEventListener(this.updatePopups, this);
}
diff --git a/src/cesiumjs/manager/SceneManager.js b/src/cesiumjs/manager/SceneManager.js
index 2c0562c64..3b8a439c6 100644
--- a/src/cesiumjs/manager/SceneManager.js
+++ b/src/cesiumjs/manager/SceneManager.js
@@ -137,7 +137,8 @@ export default class SceneManager {
longitudeString = Cesium.Math.toDegrees(cartographic.longitude);
latitudeString = Cesium.Math.toDegrees(cartographic.latitude);
cameraHeight = Math.ceil(that.viewer.camera.positionCartographic.height);
- height = Math.max(that.viewer.scene.globe.getHeight(cartographic), cartographic.height);
+ // height = Math.max(that.viewer.scene.globe.getHeight(cartographic), cartographic.height);
+ height = cartographic.height;
longlatHeight = `经度:${longitudeString.toFixed(4)}°,纬度:${latitudeString.toFixed(4)}°,海拔高度:${height.toFixed(0)}米,相机视角高度:${cameraHeight.toFixed(0)}米`;
}
let strHpr = '';
diff --git a/src/cesiumjs/manager/WebSceneControl.js b/src/cesiumjs/manager/WebSceneControl.js
new file mode 100644
index 000000000..9d2370698
--- /dev/null
+++ b/src/cesiumjs/manager/WebSceneControl.js
@@ -0,0 +1,270 @@
+import { CesiumZondy } from '../core/Base';
+
+/**
+ * 三维视图的主要类
+ * @alias WebSceneControl
+ * @constructor
+ * @class module:客户端视图管理.WebSceneControl
+ * @param {Element|String} elementId 放置视图的div的id
+ * @param {Object} [options] 包含以下属性的对象
+ * @param {String} [options.viewerMode=‘3D’] 初始视图模式默认为三维球视图 '2D'表示二维视图 'COLUMBUS_VIEW' 表示三维平面视图
+ * @param {Boolean} [options.showInfo=false] 是否显示默认的属性信息框
+ * @param {Boolean} [options.animation=true] 默认动画控制不显示
+ * @param {Boolean} [options.baseLayerPicker=true] If set to false, the BaseLayerPicker widget will not be created.
+ * @param {Boolean} [options.fullscreenButton=true] If set to false, the FullscreenButton widget will not be created.
+ * @param {Boolean} [options.vrButton=false] If set to true, the VRButton widget will be created.
+ * @param {Boolean} [options.onCopy=false] 是否禁用复制,默认为false禁用
+ * @example
+ * var webGlobe = new CesiumZondy.WebSceneControl('GlobeView');
+ *
+ * var webGlobe = new CesiumZondy.WebSceneControl('GlobeView',{showInfo:true});
+ * //或者如下
+ * var options ={
+ * showInfo:false,
+ * viewerMode:'3D',
+ * keyEventEnable:false
+ * };
+ * var webGlobe = new CesiumZondy.WebSceneControl('GlobeView',options);
+ */
+export default class WebSceneControl {
+ constructor(elementId, op) {
+ const options = Cesium.defaultValue(op, {});
+
+ /** 默认动画控制不显示 */
+ options.animation = Cesium.defaultValue(options.animation, false);
+ // 默认不显示图层控制显示
+ options.baseLayerPicker = Cesium.defaultValue(options.baseLayerPicker, false);
+ // 默认不显示全屏控制按钮
+ options.fullscreenButton = Cesium.defaultValue(options.fullscreenButton, false);
+ // 默认不显示地名查询框
+ options.geocoder = Cesium.defaultValue(options.geocoder, false);
+ // 默认不显示复位按钮
+ options.homeButton = Cesium.defaultValue(options.homeButton, false);
+ // 默认不显示信息框
+ options.infoBox = Cesium.defaultValue(options.infoBox, false);
+ // 默认不显示3D/2D选择器
+ options.sceneModePicker = Cesium.defaultValue(options.sceneModePicker, false);
+ // 默认不显示选取指示器组件
+ options.selectionIndicator = Cesium.defaultValue(options.selectionIndicator, false);
+ // 默认创建但不显示时间轴
+ options.timeline = Cesium.defaultValue(options.timeline, false);
+ // 默认不显示帮助按钮
+ options.navigationHelpButton = Cesium.defaultValue(options.navigationHelpButton, false);
+
+ options.navigationInstructionsInitiallyVisible = Cesium.defaultValue(options.navigationInstructionsInitiallyVisible, true);
+ // 默认不显示渲染错误信息面板
+ options.showRenderLoopErrors = Cesium.defaultValue(options.showRenderLoopErrors, false);
+ // 默认场景为三维球面视图
+ options.sceneMode = Cesium.defaultValue(options.sceneMode, Cesium.SceneMode.SCENE3D);
+ // 默认地图投影为web 墨卡托
+ options.mapProjection = Cesium.defaultValue(options.mapProjection, new Cesium.WebMercatorProjection());
+ // 默认可视化数据源集合
+ options.dataSources = Cesium.defaultValue(options.dataSources, new Cesium.DataSourceCollection());
+ // 默认支持阴影
+ options.shadows = Cesium.defaultValue(options.shadows, false);
+
+ // 使用 ThreeJS 默认要关闭自动渲染
+ if (this._useThreeJs) {
+ options.useDefaultRenderLoop = false;
+ }
+
+ this._threeContainer = undefined;
+
+ // 管理append添加的图层组
+ this._appendCollection = [];
+
+ // 默认支持键盘事件
+ this._keyEventEnable = Cesium.defaultValue(options.keyEventEnable, true);
+ // 创建默认视图对象
+ this._viewer = new Cesium.Viewer(elementId, options);
+
+ //隐藏版权信息
+ this._viewer.cesiumWidget.creditContainer.style.display = 'none';
+
+ // 场景对象
+ this._scene = this._viewer.scene;
+
+ this._screenSpaceEventHandler = new Cesium.ScreenSpaceEventHandler(this._viewer.scene.canvas);
+
+ this._elementID = elementId;
+
+ this._popupContain = []; // 用于管理多个popup,主要考虑到多个popup场景变化时需响应其事件,改变其位置
+
+ const screenSpaceCameraController = this._viewer.scene.screenSpaceCameraController;
+ //默认关闭hdr
+ this._viewer.scene.highDynamicRange = false;
+ screenSpaceCameraController.minimumZoomDistance = 1;
+ screenSpaceCameraController.maximumZoomDistance = 2400000000000000;
+ this._viewer.canvas.onclick = function () {
+ this.focus();
+ };
+
+ this._cameraParameter = {};
+ const flags = {
+ looking: false,
+ rotateLeft: false,
+ rotateRight: false,
+ moveUp: false,
+ moveDown: false,
+ moveLeft: false,
+ moveRight: false,
+ goHome: false,
+ wireFrame: false,
+ showFPS: false
+ };
+
+ //与activex球保持一致
+ function getFlagForKeyCode(keyCode) {
+ switch (keyCode) {
+ case 'W'.charCodeAt(0): //向下平移镜头
+ return 'moveDown';
+ case 'S'.charCodeAt(0): //向上平移镜头
+ return 'moveUp';
+ case 'A'.charCodeAt(0): //向右平移镜头
+ return 'moveRight';
+ case 'D'.charCodeAt(0): //向左平移镜头
+ return 'moveLeft';
+ case 'Q'.charCodeAt(0): //向右旋转镜头
+ return 'rotateRight';
+ case 'E'.charCodeAt(0): //向左旋转镜头
+ return 'rotateLeft';
+ case 'Z'.charCodeAt(0): //空格键复位
+ return 'goHome';
+ case 'G'.charCodeAt(0): //G键显示网
+ return 'wireFrame';
+ case 'F'.charCodeAt(0): //F键显示帧率
+ return 'showFPS';
+ default:
+ return undefined;
+ }
+ }
+
+ document.addEventListener(
+ 'keydown',
+ function (e) {
+ const flagName = getFlagForKeyCode(e.keyCode);
+ if (typeof flagName !== 'undefined') {
+ flags[flagName] = true;
+ }
+ },
+ false
+ );
+ document.addEventListener(
+ 'keyup',
+ function (e) {
+ const flagName = getFlagForKeyCode(e.keyCode);
+ if (typeof flagName !== 'undefined') {
+ flags[flagName] = false;
+ }
+ },
+ false
+ );
+
+ this._shouldAnimate = Cesium.defaultValue(options.shouldAnimate, false); //记录全局是否允许动画
+ var that = this;
+
+ this._viewer.clock.onTick.addEventListener(function () {
+ //获取相机高度
+ if (that.keyEventEnable) {
+ const position = that._viewer.camera.position;
+ const cameraHeight = that._viewer.scene.globe.ellipsoid.cartesianToCartographic(position).height;
+ const moveRate = cameraHeight / 40.0;
+ if (flags.rotateLeft) {
+ that._viewer.camera.rotateLeft(0.01);
+ }
+ if (flags.rotateRight) {
+ that._viewer.camera.rotateRight(0.01);
+ }
+ if (flags.moveUp) {
+ that._viewer.camera.moveBackward(moveRate);
+ }
+ if (flags.moveDown) {
+ that._viewer.camera.moveForward(moveRate);
+ }
+ if (flags.moveLeft) {
+ that._viewer.camera.moveLeft(moveRate);
+ }
+ if (flags.moveRight) {
+ that._viewer.camera.moveRight(moveRate);
+ }
+ if (flags.goHome) {
+ that._viewer.camera.flyTo({
+ destination: Cesium.Cartesian3.fromDegrees(104, 30, 15682725)
+ });
+ }
+ if (flags.wireFrame) {
+ var bShowWireframe = that._viewer.scene.globe._surface.tileProvider._debug.wireframe;
+ that._viewer.scene.globe._surface.tileProvider._debug.wireframe = !bShowWireframe;
+ flags.wireFrame = false;
+ }
+ if (flags.showFPS) {
+ var bShowFPS = that._viewer.scene.debugShowFramesPerSecond;
+ that._viewer.scene.debugShowFramesPerSecond = !bShowFPS;
+ flags.showFPS = false;
+ }
+ }
+ });
+ /**
+ * 禁用右键菜单
+ */
+ document.oncontextmenu = function () {
+ event.returnValue = false;
+ };
+ // /**
+ // * 禁用选中功能
+ // */
+ // document.onselectstart = function(){
+ // event.returnValue = false;
+ // };
+ /**
+ * 禁用复制功能
+ */
+ document.oncopy = function () {
+ event.returnValue = that._onCopy;
+ };
+
+ this.scene.skyAtmosphere.showGroundAtmosphere = false;
+ this._isRecoverExplosion = false;
+ }
+
+ /**
+ * 视图
+ * @memberof WebSceneControl.prototype
+ * @type {Viewer}
+ * @readonly
+ */
+ get viewer() {
+ return this._viewer;
+ }
+
+ /**
+ * 场景
+ * @memberof WebSceneControl.prototype
+ * @readonly
+ * @type {Scene}
+ */
+ get scene() {
+ return this._scene;
+ }
+
+ /**
+ * 事件句柄
+ * @memberof WebSceneControl.prototype
+ * @readonly
+ */
+ get screenSpaceEventHandler() {
+ return this._screenSpaceEventHandler;
+ }
+
+ /**
+ * 当前椭球
+ * @memberof WebSceneControl.prototype
+ * @type {Ellipsoid}
+ * @readonly
+ */
+ get ellipsoid() {
+ return this._viewer.scene.globe.ellipsoid;
+ }
+}
+
+CesiumZondy.WebSceneControl = WebSceneControl;
diff --git a/src/cesiumjs/manager/index.js b/src/cesiumjs/manager/index.js
index 3e6bb68e2..4dd2436f7 100644
--- a/src/cesiumjs/manager/index.js
+++ b/src/cesiumjs/manager/index.js
@@ -8,6 +8,7 @@ import LabelLayer from "./LabelLayer";
import MouseEventManager from "./MouseEventManager";
import PopupController from "./PopupController";
import SceneManager from "./SceneManager";
+import WebSceneControl from './WebSceneControl';
export {
AnalysisManager,
@@ -19,4 +20,5 @@ export {
MouseEventManager,
PopupController,
SceneManager,
+ WebSceneControl
};
\ No newline at end of file
diff --git a/src/cesiumjs/overlay/MapvLayer.js b/src/cesiumjs/overlay/MapvLayer.js
index c81e5829e..9a08ec0ff 100644
--- a/src/cesiumjs/overlay/MapvLayer.js
+++ b/src/cesiumjs/overlay/MapvLayer.js
@@ -15,7 +15,7 @@ var idIndex = 0;
* @param {Boolean} [mapVOptions.cesium.postRender=false] 是否实时渲染
* @param {Boolean} [mapVOptionscesium.cesium.postRenderFrame=30] 每间隔多少帧渲染一次
* @param container - {Element} 外部传入的div;外接的方式使用mapv
- * @example
+ * @example
* // 构建对应的dataset
var dataSet = new mapv.DataSet(data);
@@ -65,6 +65,7 @@ export default class MapvLayer {
this.canvas = this._creteWidgetCanvas(); //this._createCanvas();
this.render = this.render.bind(this);
+ this.handler = undefined;
this.postRenderTime = 0;
let cesiumOpt = mapVOptions.cesium;
@@ -101,7 +102,7 @@ export default class MapvLayer {
bindEvent() {
let self = this;
- var map = this.map;
+ let map = this.map;
//下面几个是cesium专属事件,clickEvent和mousemoveEvent是mapv内部自带的方法不放出来
this.innerMoveStart = this.moveStartEvent.bind(this);
this.innerMoveEnd = this.moveEndEvent.bind(this);
@@ -113,30 +114,42 @@ export default class MapvLayer {
this.postStartEvent = this.postStartEvent.bind(this);
this.postEndEvent = this.postEndEvent.bind(this);
- var handler = new Cesium.ScreenSpaceEventHandler(this.scene.canvas);
//handler.setInputAction(this.innerMoveEnd, Cesium.ScreenSpaceEventType.MOUSE_MOVE);
if (this.postRender) {
// this.scene.postRender.addEventListener(this.postEventHandle);
this.scene.camera.moveStart.addEventListener(this.postStartEvent, this);
this.scene.camera.moveEnd.addEventListener(this.postEndEvent, this);
} else {
+ var handler = new Cesium.ScreenSpaceEventHandler(this.scene.canvas);
+
handler.setInputAction(this.innerMoveEnd, Cesium.ScreenSpaceEventType.WHEEL);
handler.setInputAction(this.innerMoveStart, Cesium.ScreenSpaceEventType.LEFT_DOWN);
handler.setInputAction(this.innerMoveEnd, Cesium.ScreenSpaceEventType.LEFT_UP);
handler.setInputAction(this.innerMoveStart, Cesium.ScreenSpaceEventType.RIGHT_DOWN);
handler.setInputAction(this.innerMoveEnd, Cesium.ScreenSpaceEventType.RIGHT_UP);
- map.scene.camera.moveEnd.addEventListener(function () {
- //获取当前相机高度
- self.innerMoveEnd();
- });
+ map.scene.camera.moveEnd.addEventListener(this.innerMoveEnd(), this);
+
+ this.handler = handler;
}
}
unbindEvent() {
+ let map = this.map;
if (this.postRender) {
this.scene.camera.moveStart.removeEventListener(this.postStartEvent, this);
this.scene.camera.moveEnd.removeEventListener(this.postEndEvent, this);
+ } else {
+ let handler = this.handler;
+ if (handler) {
+ handler.removeInputAction(this.innerMoveEnd, Cesium.ScreenSpaceEventType.WHEEL);
+ handler.removeInputAction(this.innerMoveStart, Cesium.ScreenSpaceEventType.LEFT_DOWN);
+ handler.removeInputAction(this.innerMoveEnd, Cesium.ScreenSpaceEventType.LEFT_UP);
+ handler.removeInputAction(this.innerMoveStart, Cesium.ScreenSpaceEventType.RIGHT_DOWN);
+ handler.removeInputAction(this.innerMoveEnd, Cesium.ScreenSpaceEventType.RIGHT_UP);
+ handler.destroy();
+ }
+ map.scene.camera.moveEnd.removeEventListener(this.innerMoveEnd(), this);
}
}
@@ -261,10 +274,19 @@ export default class MapvLayer {
canvas.style.pointerEvents = 'none';
canvas.style.zIndex = this.mapVOptions.zIndex || 100;
- canvas.width = parseInt(this.map.canvas.width);
- canvas.height = parseInt(this.map.canvas.height);
- canvas.style.width = this.map.canvas.style.width;
- canvas.style.height = this.map.canvas.style.height;
+ // canvas.width = parseInt(this.map.canvas.width);
+ // canvas.height = parseInt(this.map.canvas.height);
+ // canvas.style.width = this.map.canvas.style.width;
+ // canvas.style.height = this.map.canvas.style.height;
+ canvas.width =
+ parseInt(this.map.canvas.width) ||
+ parseInt(this.map.container.offsetWidth);
+ canvas.height =
+ parseInt(this.map.canvas.height) ||
+ parseInt(this.map.container.offsetHeight);
+ canvas.style.width = parseInt(this.map.container.offsetWidth) + "px";
+ canvas.style.height = parseInt(this.map.container.offsetHeight) + "px";
+
var devicePixelRatio = this.devicePixelRatio;
if (this.mapVOptions.context == '2d') {
canvas.getContext(this.mapVOptions.context).scale(devicePixelRatio, devicePixelRatio);
@@ -283,10 +305,18 @@ export default class MapvLayer {
canvas.style.pointerEvents = 'none';
canvas.style.zIndex = this.mapVOptions.zIndex || 100;
- canvas.width = parseInt(this.map.canvas.width);
- canvas.height = parseInt(this.map.canvas.height);
- canvas.style.width = this.map.canvas.style.width;
- canvas.style.height = this.map.canvas.style.height;
+ // canvas.width = parseInt(this.map.canvas.width);
+ // canvas.height = parseInt(this.map.canvas.height);
+ // canvas.style.width = this.map.canvas.style.width;
+ // canvas.style.height = this.map.canvas.style.height;
+ canvas.width =
+ parseInt(this.map.canvas.width) ||
+ parseInt(this.map.container.offsetWidth);
+ canvas.height =
+ parseInt(this.map.canvas.height) ||
+ parseInt(this.map.container.offsetHeight);
+ canvas.style.width = parseInt(this.map.container.offsetWidth) + "px";
+ canvas.style.height = parseInt(this.map.container.offsetHeight) + "px";
var devicePixelRatio = this.devicePixelRatio;
if (this.mapVOptions.context == '2d') {
canvas.getContext('2d').scale(devicePixelRatio, devicePixelRatio);
@@ -339,8 +369,8 @@ export default class MapvLayer {
*/
remove() {
if (this.mapvBaseLayer == undefined) return;
- this.removeAllData();
this.unbindEvent();
+ this.removeAllData();
this.mapvBaseLayer.clear(this.mapvBaseLayer.getContext());
this.mapvBaseLayer = undefined;
var parent = this.canvas.parentElement;
@@ -365,10 +395,18 @@ export default class MapvLayer {
canvas.style.position = 'absolute';
canvas.style.top = '0px';
canvas.style.left = '0px';
- canvas.width = parseInt(this.map.canvas.width);
- canvas.height = parseInt(this.map.canvas.height);
+ // canvas.width = parseInt(this.map.canvas.width);
+ // canvas.height = parseInt(this.map.canvas.height);
//canvas.style.width = this.map.canvas.style.width;
//canvas.style.height = this.map.canvas.style.height;
+ canvas.width =
+ parseInt(this.map.canvas.width) ||
+ parseInt(this.map.container.offsetWidth) * this.devicePixelRatio;
+ canvas.height =
+ parseInt(this.map.canvas.height) ||
+ parseInt(this.map.container.offsetHeight) * this.devicePixelRatio;
+ canvas.style.width = parseInt(this.map.container.offsetWidth) + 'px';
+ canvas.style.height = parseInt(this.map.container.offsetHeight) + 'px';
var devicePixelRatio = this.devicePixelRatio;
if (this.mapVOptions.context == '2d') {
canvas.getContext('2d').scale(devicePixelRatio, devicePixelRatio);
diff --git a/src/cesiumjs/overlay/PopupLayer.js b/src/cesiumjs/overlay/PopupLayer.js
index f60bcd4fd..e8c34f718 100644
--- a/src/cesiumjs/overlay/PopupLayer.js
+++ b/src/cesiumjs/overlay/PopupLayer.js
@@ -1,7 +1,6 @@
import { CesiumZondy } from '../core/Base';
import { updataPopupPosition } from './popup/popup';
-import Cesium from '../../../node_modules/cesium/Source/Cesium';
var popupsIdIndex = 0;
@@ -20,6 +19,9 @@ var popupsIdIndex = 0;
* @param {String} [options.popupContentId] 本次popup对应的唯一内容id
* @param {Boolean} [options.postRender=true] 是否实时渲染
* @param {Boolean} [options.showClose=true] 是否显示关闭按钮
+ * @param {Object} [options.callback]
+ * @param {Function} [options.callback.onShow] 显示popup事件的回调
+ * @param {Function} [options.callback.onHide] 隐藏popup事件的回调
* @param {Element|String} container 外部传入的div的字符串描述方式,一般是文字或者echarts的div;
*
* @example 这里唯一要注意的是我们中地数码的ceisum的右键事件不是放大缩小而是旋转视角
@@ -69,41 +71,59 @@ export default class PopupLayer {
this.popupContentId = options.popupContentId || 'cesium-popup-content-id-' + popupsIdIndex++;
this.options.postRender = this.options.postRender === undefined ? true : this.options.postRender;
+ this.Cesium = options.Cesium || window['Cesium'];
this.scene = map.scene;
this.camera = map.camera;
this.isShow = true;
- console.log('popup 1');
- this.handler = new Cesium.ScreenSpaceEventHandler(this.scene.canvas);
+ if (options.callback) {
+ const { onShow, onHide } = options.callback;
+ this.onShow = onShow;
+ this.onHide = onHide;
+ }
+
+ let ScreenSpaceEventHandler = this.Cesium.ScreenSpaceEventHandler || window['Cesium'].ScreenSpaceEventHandler;
+
+ this.handler = new ScreenSpaceEventHandler(this.scene.canvas);
this.infoDiv = null;
// this.px_position = null;
if (position.entity) {
this.cartesian = position.entity.position._value;
}
- console.log('popup 2');
this.cartesian =
this.cartesian ||
this.position.cartesian ||
- Cesium.Cartesian3.fromDegrees(this.position.longitude, this.position.latitude, this.position.height);
+ this.Cesium.Cartesian3.fromDegrees(this.position.longitude, this.position.latitude, this.position.height);
- let parents = document.getElementsByClassName('cesium-widget');
- parent = parents.length > 0 ? parents[0] : map.container;
- this.parent = parent;
- console.log('popup parent', parent);
+ let vc = this.map.container;
+ let cesumWidgetContainer = undefined;
+ if (vc.children && vc.children.length > 0) {
+ if (vc.children[0].children && vc.children[0].children.length > 0) {
+ if (vc.children[0].children[0].children && vc.children[0].children[0].children.length > 0) {
+ cesumWidgetContainer = vc.children[0].children[0].children[0];
+ }
+ }
+ }
+
+ if (!cesumWidgetContainer) {
+ let parents = document.getElementsByClassName('cesium-widget');
+ parent = parents.length > 0 ? parents[0] : map.container;
+ this.parent = parent;
+ } else {
+ this.parent = cesumWidgetContainer;
+ }
// this.initDevicePixelRatio();
this.showClose = options.showClose === undefined ? true : options.showClose;
this.popup = this._createPopup();
- console.log('popup 3');
this.moveStart = this.eventMoveStart.bind(this);
this.moveEnd = this.eventMoveEnd.bind(this);
this.movement = this.movement.bind(this);
this.update = this.update.bind(this);
- console.log('popup 4');
this.bindEvent();
@@ -116,18 +136,34 @@ export default class PopupLayer {
let infoDiv = window.document.createElement('div');
infoDiv.id = this.popupId;
infoDiv.style.display = 'none';
- infoDiv.innerHTML =
- '';
+ if (typeof this.container === 'string') {
+ infoDiv.innerHTML =
+ '';
+ } else {
+ let popupContentDiv = window.document.createElement('div');
+ popupContentDiv.id = this.popupContentId;
+ popupContentDiv.className = 'cesium-popup';
+ let popupContentWrapperDiv = window.document.createElement('div');
+ popupContentWrapperDiv.className = 'cesium-popup-content-wrapper';
+ popupContentWrapperDiv.appendChild(this.container);
+ popupContentDiv.appendChild(popupContentWrapperDiv);
+
+ let tipContainerDiv = window.document.createElement('div');
+ tipContainerDiv.className = 'cesium-popup-tip-container';
+ let tipDiv = window.document.createElement('div');
+ tipDiv.className = 'cesium-popup-tip';
+ tipContainerDiv.appendChild(tipDiv);
+ popupContentDiv.appendChild(tipContainerDiv);
+ infoDiv.appendChild(popupContentDiv);
+ }
+
let close = window.document.createElement('div');
close.className = 'cesium-popup-close-button';
close.addEventListener('click', () => self.hide());
@@ -143,7 +179,7 @@ export default class PopupLayer {
bindEvent() {
let self = this;
- this.handler.setInputAction(this.movement, Cesium.ScreenSpaceEventType.LEFT_CLICK);
+ this.handler.setInputAction(this.movement, this.Cesium.ScreenSpaceEventType.LEFT_CLICK);
if (!this.map) {
return;
}
@@ -152,8 +188,8 @@ export default class PopupLayer {
this.map.scene.postRender.addEventListener(() => self.update());
} else {
this.map.camera.changed.addEventListener(() => self.update());
- this.handler.setInputAction(this.moveStart, Cesium.ScreenSpaceEventType.LEFT_DOWN);
- this.handler.setInputAction(this.moveEnd, Cesium.ScreenSpaceEventType.LEFT_UP);
+ this.handler.setInputAction(this.moveStart, this.Cesium.ScreenSpaceEventType.LEFT_DOWN);
+ this.handler.setInputAction(this.moveEnd, this.Cesium.ScreenSpaceEventType.LEFT_UP);
this.map.scene.camera.moveEnd.addEventListener(() => self.update());
}
}
@@ -175,8 +211,8 @@ export default class PopupLayer {
movement(movement) {
var pickedPrimitive = this.map.scene.pick(movement.position);
- var pickedEntity = Cesium.defined(pickedPrimitive) ? pickedPrimitive.id : undefined;
- if (Cesium.defined(pickedEntity) /* && Cesium.defined(pickedEntity.billboard) */) {
+ var pickedEntity = this.Cesium.defined(pickedPrimitive) ? pickedPrimitive.id : undefined;
+ if (this.Cesium.defined(pickedEntity) /* && Cesium.defined(pickedEntity.billboard) */) {
if (this.position && this.position.entity) {
pickedPrimitive.id === this.position.entity.id;
this.show();
@@ -204,6 +240,9 @@ export default class PopupLayer {
*/
show() {
this.isShow = true;
+ if (this.onShow) {
+ this.onShow(this.isShow);
+ }
let node = window.document.getElementById(this.popupId);
if (node && node.style) {
node.style.display = 'block';
@@ -216,6 +255,9 @@ export default class PopupLayer {
*/
hide() {
this.isShow = false;
+ if (this.onHide) {
+ this.onHide(this.isShow);
+ }
let node = window.document.getElementById(this.popupId);
if (node && node.style) {
node.style.display = 'none';
diff --git a/src/cesiumjs/overlay/mapv/MapvBaseLayer.js b/src/cesiumjs/overlay/mapv/MapvBaseLayer.js
index aa761b1e8..454141f50 100644
--- a/src/cesiumjs/overlay/mapv/MapvBaseLayer.js
+++ b/src/cesiumjs/overlay/mapv/MapvBaseLayer.js
@@ -287,16 +287,18 @@ export class MapvBaseLayer extends BaseLayer {
}
});
this.dataSet.set(newData);
- this.update({
+ this.stopAniamation = true;
+ /* this.update({
options: null
- });
+ }); */
}
clearData() {
this.dataSet && this.dataSet.clear();
- this.update({
+ this.stopAniamation = true;
+ /* this.update({
options: null
- });
+ }); */
}
draw() {
diff --git a/src/cesiumjs/overlay/popup/popup.js b/src/cesiumjs/overlay/popup/popup.js
index a95127408..0fc8388fa 100644
--- a/src/cesiumjs/overlay/popup/popup.js
+++ b/src/cesiumjs/overlay/popup/popup.js
@@ -1,5 +1,3 @@
-import Cesium from '../../../../node_modules/cesium/Source/Cesium';
-
/**
* @description 用来调整相机视角的时候设置对应的
* @param {Viewer} viewer Cesium的viewer对象
@@ -10,56 +8,57 @@ import Cesium from '../../../../node_modules/cesium/Source/Cesium';
* @param {Object} [options.latitude] 传入的纬度,内部换算笛卡尔积
*/
export function updataPopupPosition(viewer, cartesian, popupId, popupContentId, options) {
- if(!cartesian) return ;
+ if (!cartesian) return;
let scene = viewer.scene;
let camera = viewer.camera;
let rect = camera.computeViewRectangle();
- const south = Cesium.Math.toDegrees(rect.south)
- const north = Cesium.Math.toDegrees(rect.north)
- const east = Cesium.Math.toDegrees(rect.east)
- const west = Cesium.Math.toDegrees(rect.west)
+ let Cesium = options.Cesium || window['Cesium'];
+ const south = Cesium.Math.toDegrees(rect.south);
+ const north = Cesium.Math.toDegrees(rect.north);
+ const east = Cesium.Math.toDegrees(rect.east);
+ const west = Cesium.Math.toDegrees(rect.west);
let carto, longitude, latitude;
- if(options && options.longitude && options.latitude){
+ if (options && options.longitude && options.latitude) {
longitude = options.position.longitude;
latitude = options.position.latitude;
if (longitude < west || longitude > east || latitude > north || latitude < south) {
- popup.style.display = "none";
+ popup.style.display = 'none';
return;
}
} else {
- carto = Cesium.Ellipsoid.WGS84.cartesianToCartographic(cartesian);
+ carto = Cesium.Ellipsoid.WGS84.cartesianToCartographic(cartesian);
longitude = Cesium.Math.toDegrees(carto.longitude);
latitude = Cesium.Math.toDegrees(carto.latitude);
}
-
+
var px_position = Cesium.SceneTransforms.wgs84ToWindowCoordinates(scene, cartesian);
-
- if(!px_position) return;
+
+ if (!px_position) return;
var res = false;
var e = cartesian,
i = camera.position,
n = scene.globe.ellipsoid.cartesianToCartographic(i).height;
- if (!(n += 1 * scene.globe.ellipsoid.maximumRadius, Cesium.Cartesian3.distance(i, e) > n)) {
+ if (!((n += 1 * scene.globe.ellipsoid.maximumRadius), Cesium.Cartesian3.distance(i, e) > n)) {
res = true;
}
if (longitude < west || longitude > east || latitude > north || latitude < south) {
res = false;
}
-
+
let popup = window.document.getElementById(popupId);
- if(!popup) return;
+ if (!popup) return;
if (res) {
- popup.style.display = "block";
+ popup.style.display = 'block';
var trackPopUpContent = window.document.getElementById(popupContentId);
var popw = document.getElementById(popupContentId).offsetWidth;
var poph = document.getElementById(popupContentId).offsetHeight;
- trackPopUpContent.style.left = px_position.x - (popw / 2) + "px";
- trackPopUpContent.style.top = px_position.y - (poph - 10) + "px";
+ trackPopUpContent.style.left = px_position.x - popw / 2 + 'px';
+ trackPopUpContent.style.top = px_position.y - (poph - 10) + 'px';
} else {
- popup.style.display = "none";
+ popup.style.display = 'none';
}
-}
\ No newline at end of file
+}
diff --git a/src/cesiumjs/provider/IgsDocProvider.js b/src/cesiumjs/provider/IgsDocProvider.js
index 0b7e4e779..4808653b2 100644
--- a/src/cesiumjs/provider/IgsDocProvider.js
+++ b/src/cesiumjs/provider/IgsDocProvider.js
@@ -1,4 +1,4 @@
-import Cesium from "../../../node_modules/cesium/Source/Cesium";
+// import Cesium from "../../../node_modules/cesium/Source/Cesium";
import { CesiumZondy } from "../core/Base";
var defaultCreditZondy = new Cesium.Credit("MapGISMap");
diff --git a/src/cesiumjs/provider/IgsTileProvider.js b/src/cesiumjs/provider/IgsTileProvider.js
index 20132dff1..aad05cd8e 100644
--- a/src/cesiumjs/provider/IgsTileProvider.js
+++ b/src/cesiumjs/provider/IgsTileProvider.js
@@ -1,4 +1,4 @@
-import Cesium from "../../../node_modules/cesium/Source/Cesium";
+// import Cesium from "../../../node_modules/cesium/Source/Cesium";
import { CesiumZondy } from "../core/Base";
var defaultCreditZondy = new Cesium.Credit("MapGISMap");
diff --git a/src/cesiumjs/provider/WebReverseMapServiceImageryProvider.js b/src/cesiumjs/provider/WebReverseMapServiceImageryProvider.js
new file mode 100644
index 000000000..c3867a05d
--- /dev/null
+++ b/src/cesiumjs/provider/WebReverseMapServiceImageryProvider.js
@@ -0,0 +1,465 @@
+import { CesiumZondy } from "../core/Base";
+
+import defaultValue from '../../../node_modules/cesium/Source/Core/defaultValue';
+import defined from '../../../node_modules/cesium/Source/Core/defined';
+// import defineProperties from '../../../node_modules/cesium/Source/Core/defineProperties';
+import DeveloperError from '../../../node_modules/cesium/Source/Core/DeveloperError';
+import GeographicTilingScheme from '../../../node_modules/cesium/Source/Core/GeographicTilingScheme';
+import Resource from '../../../node_modules/cesium/Source/Core/Resource';
+import WebMercatorProjection from '../../../node_modules/cesium/Source/Core/WebMercatorProjection';
+import GetFeatureInfoFormat from '../../../node_modules/cesium/Source/Scene/GetFeatureInfoFormat';
+import UrlTemplateImageryProvider from '../../../node_modules/cesium/Source/Scene/UrlTemplateImageryProvider';
+
+let freezeObject = Object.freeze;
+
+/**
+ * Provides tiled imagery hosted by a Web Map Service (WMS) server.
+ *
+ * @alias WebReverseMapServiceImageryProvider
+ * @constructor
+ *
+ * @param {Object} options Object with the following properties:
+ * @param {Resource|String} options.url The URL of the WMS service. The URL supports the same keywords as the {@link UrlTemplateImageryProvider}.
+ * @param {String} options.layers The layers to include, separated by commas.
+ * @param {Object} [options.parameters=WebReverseMapServiceImageryProvider.DefaultParameters] Additional parameters to pass to the WMS server in the GetMap URL.
+ * @param {Object} [options.getFeatureInfoParameters=WebReverseMapServiceImageryProvider.GetFeatureInfoDefaultParameters] Additional parameters to pass to the WMS server in the GetFeatureInfo URL.
+ * @param {Boolean} [options.enablePickFeatures=true] If true, {@link WebReverseMapServiceImageryProvider#pickFeatures} will invoke
+ * the GetFeatureInfo operation on the WMS server and return the features included in the response. If false,
+ * {@link WebReverseMapServiceImageryProvider#pickFeatures} will immediately return undefined (indicating no pickable features)
+ * without communicating with the server. Set this property to false if you know your WMS server does not support
+ * GetFeatureInfo or if you don't want this provider's features to be pickable. Note that this can be dynamically
+ * overridden by modifying the WebReverseMapServiceImageryProvider#enablePickFeatures property.
+ * @param {GetFeatureInfoFormat[]} [options.getFeatureInfoFormats=WebReverseMapServiceImageryProvider.DefaultGetFeatureInfoFormats] The formats
+ * in which to try WMS GetFeatureInfo requests.
+ * @param {Rectangle} [options.rectangle=Rectangle.MAX_VALUE] The rectangle of the layer.
+ * @param {TilingScheme} [options.tilingScheme=new GeographicTilingScheme()] The tiling scheme to use to divide the world into tiles.
+ * @param {Ellipsoid} [options.ellipsoid] The ellipsoid. If the tilingScheme is specified,
+ * this parameter is ignored and the tiling scheme's ellipsoid is used instead. If neither
+ * parameter is specified, the WGS84 ellipsoid is used.
+ * @param {Number} [options.tileWidth=256] The width of each tile in pixels.
+ * @param {Number} [options.tileHeight=256] The height of each tile in pixels.
+ * @param {Number} [options.minimumLevel=0] The minimum level-of-detail supported by the imagery provider. Take care when
+ * specifying this that the number of tiles at the minimum level is small, such as four or less. A larger number is
+ * likely to result in rendering problems.
+ * @param {Number} [options.maximumLevel] The maximum level-of-detail supported by the imagery provider, or undefined if there is no limit.
+ * If not specified, there is no limit.
+ * @param {String} [options.crs] CRS specification, for use with WMS specification >= 1.3.0.
+ * @param {String} [options.srs] SRS specification, for use with WMS specification 1.1.0 or 1.1.1
+ * @param {Credit|String} [options.credit] A credit for the data source, which is displayed on the canvas.
+ * @param {String|String[]} [options.subdomains='abc'] The subdomains to use for the {s}
placeholder in the URL template.
+ * If this parameter is a single string, each character in the string is a subdomain. If it is
+ * an array, each element in the array is a subdomain.
+ *
+ * @see ArcGisMapServerImageryProvider
+ * @see BingMapsImageryProvider
+ * @see GoogleEarthEnterpriseMapsProvider
+ * @see createOpenStreetMapImageryProvider
+ * @see SingleTileImageryProvider
+ * @see createTileMapServiceImageryProvider
+ * @see WebMapTileServiceImageryProvider
+ * @see UrlTemplateImageryProvider
+ *
+ * @see {@link http://resources.esri.com/help/9.3/arcgisserver/apis/rest/|ArcGIS Server REST API}
+ * @see {@link http://www.w3.org/TR/cors/|Cross-Origin Resource Sharing}
+ *
+ * @example
+ * var provider = new Cesium.WebReverseMapServiceImageryProvider({
+ * url : 'https://sampleserver1.arcgisonline.com/ArcGIS/services/Specialty/ESRI_StatesCitiesRivers_USA/MapServer/WMSServer',
+ * layers : '0',
+ * proxy: new Cesium.DefaultProxy('/proxy/')
+ * });
+ *
+ * viewer.imageryLayers.addImageryProvider(provider);
+ */
+export class WebReverseMapServiceImageryProvider {
+ constructor(options) {
+ options = defaultValue(options, defaultValue.EMPTY_OBJECT);
+
+ //>>includeStart('debug', pragmas.debug);
+ if (!defined(options.url)) {
+ throw new DeveloperError('options.url is required.');
+ }
+ if (!defined(options.layers)) {
+ throw new DeveloperError('options.layers is required.');
+ }
+ //>>includeEnd('debug');
+
+ var resource = Resource.createIfNeeded(options.url);
+ var pickFeatureResource = resource.clone();
+
+ resource.setQueryParameters(WebReverseMapServiceImageryProvider.DefaultParameters, true);
+ pickFeatureResource.setQueryParameters(WebReverseMapServiceImageryProvider.GetFeatureInfoDefaultParameters, true);
+
+ if (defined(options.parameters)) {
+ resource.setQueryParameters(objectToLowercase(options.parameters));
+ }
+
+ if (defined(options.getFeatureInfoParameters)) {
+ pickFeatureResource.setQueryParameters(objectToLowercase(options.getFeatureInfoParameters));
+ }
+
+ let { reversebbox = false} = options;
+
+ var parameters = {};
+ parameters.layers = options.layers;
+ if (reversebbox) {
+ parameters.bbox = '{southProjected},{westProjected},{northProjected},{eastProjected}';
+ } else {
+ parameters.bbox = '{westProjected},{southProjected},{eastProjected},{northProjected}';
+ }
+
+ parameters.width = '{width}';
+ parameters.height = '{height}';
+
+ // Use SRS or CRS based on the WMS version.
+ if (parseFloat(resource.queryParameters.version) >= 1.3) {
+ // Use CRS with 1.3.0 and going forward.
+ // For GeographicTilingScheme, use CRS:84 vice EPSG:4326 to specify lon, lat (x, y) ordering for
+ // bbox requests.
+ parameters.crs = defaultValue(
+ options.crs,
+ options.tilingScheme && options.tilingScheme.projection instanceof WebMercatorProjection ? 'EPSG:3857' : 'CRS:84'
+ );
+ } else {
+ // SRS for WMS 1.1.0 or 1.1.1.
+ parameters.srs = defaultValue(
+ options.srs,
+ options.tilingScheme && options.tilingScheme.projection instanceof WebMercatorProjection ? 'EPSG:3857' : 'EPSG:4326'
+ );
+ }
+
+ resource.setQueryParameters(parameters, true);
+ pickFeatureResource.setQueryParameters(parameters, true);
+
+ var pickFeatureParams = {
+ query_layers: options.layers,
+ x: '{i}',
+ y: '{j}',
+ info_format: '{format}'
+ };
+ pickFeatureResource.setQueryParameters(pickFeatureParams, true);
+
+ this._resource = resource;
+ this._pickFeaturesResource = pickFeatureResource;
+ this._layers = options.layers;
+
+ // Let UrlTemplateImageryProvider do the actual URL building.
+ this._tileProvider = new UrlTemplateImageryProvider({
+ url: resource,
+ pickFeaturesUrl: pickFeatureResource,
+ tilingScheme: defaultValue(options.tilingScheme, new GeographicTilingScheme({ ellipsoid: options.ellipsoid })),
+ rectangle: options.rectangle,
+ tileWidth: options.tileWidth,
+ tileHeight: options.tileHeight,
+ minimumLevel: options.minimumLevel,
+ maximumLevel: options.maximumLevel,
+ subdomains: options.subdomains,
+ tileDiscardPolicy: options.tileDiscardPolicy,
+ credit: options.credit,
+ getFeatureInfoFormats: defaultValue(options.getFeatureInfoFormats, WebReverseMapServiceImageryProvider.DefaultGetFeatureInfoFormats),
+ enablePickFeatures: options.enablePickFeatures
+ });
+
+ /**
+ * The default parameters to include in the WMS URL to obtain images. The values are as follows:
+ * service=WMS
+ * version=1.1.1
+ * request=GetMap
+ * styles=
+ * format=image/jpeg
+ *
+ * @constant
+ * @type {Object}
+ */
+ this.DefaultParameters = freezeObject({
+ service: 'WMS',
+ version: '1.1.1',
+ request: 'GetMap',
+ styles: '',
+ format: 'image/jpeg'
+ });
+
+ /**
+ * The default parameters to include in the WMS URL to get feature information. The values are as follows:
+ * service=WMS
+ * version=1.1.1
+ * request=GetFeatureInfo
+ *
+ * @constant
+ * @type {Object}
+ */
+ this.GetFeatureInfoDefaultParameters = freezeObject({
+ service: 'WMS',
+ version: '1.1.1',
+ request: 'GetFeatureInfo'
+ });
+
+ this.DefaultGetFeatureInfoFormats = freezeObject([
+ freezeObject(new GetFeatureInfoFormat('json', 'application/json')),
+ freezeObject(new GetFeatureInfoFormat('xml', 'text/xml')),
+ freezeObject(new GetFeatureInfoFormat('text', 'text/html'))
+ ]);
+ }
+
+ /**
+ * Gets the credits to be displayed when a given tile is displayed.
+ *
+ * @param {Number} x The tile X coordinate.
+ * @param {Number} y The tile Y coordinate.
+ * @param {Number} level The tile level;
+ * @returns {Credit[]} The credits to be displayed when the tile is displayed.
+ *
+ * @exception {DeveloperError} getTileCredits
must not be called before the imagery provider is ready.
+ */
+ getTileCredits(x, y, level) {
+ return this._tileProvider.getTileCredits(x, y, level);
+ }
+
+ /**
+ * Requests the image for a given tile. This function should
+ * not be called before {@link WebReverseMapServiceImageryProvider#ready} returns true.
+ *
+ * @param {Number} x The tile X coordinate.
+ * @param {Number} y The tile Y coordinate.
+ * @param {Number} level The tile level.
+ * @param {Request} [request] The request object. Intended for internal use only.
+ * @returns {Promise.|undefined} A promise for the image that will resolve when the image is available, or
+ * undefined if there are too many active requests to the server, and the request
+ * should be retried later. The resolved image may be either an
+ * Image or a Canvas DOM object.
+ *
+ * @exception {DeveloperError} requestImage
must not be called before the imagery provider is ready.
+ */
+ requestImage(x, y, level, request) {
+ return this._tileProvider.requestImage(x, y, level, request);
+ }
+
+ /**
+ * Asynchronously determines what features, if any, are located at a given longitude and latitude within
+ * a tile. This function should not be called before {@link ImageryProvider#ready} returns true.
+ *
+ * @param {Number} x The tile X coordinate.
+ * @param {Number} y The tile Y coordinate.
+ * @param {Number} level The tile level.
+ * @param {Number} longitude The longitude at which to pick features.
+ * @param {Number} latitude The latitude at which to pick features.
+ * @return {Promise.|undefined} A promise for the picked features that will resolve when the asynchronous
+ * picking completes. The resolved value is an array of {@link ImageryLayerFeatureInfo}
+ * instances. The array may be empty if no features are found at the given location.
+ *
+ * @exception {DeveloperError} pickFeatures
must not be called before the imagery provider is ready.
+ */
+ pickFeatures(x, y, level, longitude, latitude) {
+ return this._tileProvider.pickFeatures(x, y, level, longitude, latitude);
+ }
+
+ /**
+ * Gets the URL of the WMS server.
+ * @memberof WebReverseMapServiceImageryProvider.prototype
+ * @type {String}
+ * @readonly
+ */
+ get functionurl() {
+ return this._resource._url;
+ }
+
+ /**
+ * Gets the proxy used by this provider.
+ * @memberof WebReverseMapServiceImageryProvider.prototype
+ * @type {Proxy}
+ * @readonly
+ */
+
+ get proxy() {
+ return this._resource.proxy;
+ }
+
+ /**
+ * Gets the names of the WMS layers, separated by commas.
+ * @memberof WebReverseMapServiceImageryProvider.prototype
+ * @type {String}
+ * @readonly
+ */
+
+ get layers() {
+ return this._layers;
+ }
+
+ /**
+ * Gets the width of each tile, in pixels. This function should
+ * not be called before {@link WebReverseMapServiceImageryProvider#ready} returns true.
+ * @memberof WebReverseMapServiceImageryProvider.prototype
+ * @type {Number}
+ * @readonly
+ */
+
+ get tileWidth() {
+ return this._tileProvider.tileWidth;
+ }
+
+ /**
+ * Gets the height of each tile, in pixels. This function should
+ * not be called before {@link WebReverseMapServiceImageryProvider#ready} returns true.
+ * @memberof WebReverseMapServiceImageryProvider.prototype
+ * @type {Number}
+ * @readonly
+ */
+
+ get tileHeight() {
+ return this._tileProvider.tileHeight;
+ }
+
+ /**
+ * Gets the maximum level-of-detail that can be requested. This function should
+ * not be called before {@link WebReverseMapServiceImageryProvider#ready} returns true.
+ * @memberof WebReverseMapServiceImageryProvider.prototype
+ * @type {Number}
+ * @readonly
+ */
+
+ get maximumLevel() {
+ return this._tileProvider.maximumLevel;
+ }
+
+ /**
+ * Gets the minimum level-of-detail that can be requested. This function should
+ * not be called before {@link WebReverseMapServiceImageryProvider#ready} returns true.
+ * @memberof WebReverseMapServiceImageryProvider.prototype
+ * @type {Number}
+ * @readonly
+ */
+
+ get minimumLevel() {
+ return this._tileProvider.minimumLevel;
+ }
+
+ /**
+ * Gets the tiling scheme used by this provider. This function should
+ * not be called before {@link WebReverseMapServiceImageryProvider#ready} returns true.
+ * @memberof WebReverseMapServiceImageryProvider.prototype
+ * @type {TilingScheme}
+ * @readonly
+ */
+
+ get tilingScheme() {
+ return this._tileProvider.tilingScheme;
+ }
+
+ /**
+ * Gets the rectangle, in radians, of the imagery provided by this instance. This function should
+ * not be called before {@link WebReverseMapServiceImageryProvider#ready} returns true.
+ * @memberof WebReverseMapServiceImageryProvider.prototype
+ * @type {Rectangle}
+ * @readonly
+ */
+
+ get rectangle() {
+ return this._tileProvider.rectangle;
+ }
+
+ /**
+ * Gets the tile discard policy. If not undefined, the discard policy is responsible
+ * for filtering out "missing" tiles via its shouldDiscardImage function. If this function
+ * returns undefined, no tiles are filtered. This function should
+ * not be called before {@link WebReverseMapServiceImageryProvider#ready} returns true.
+ * @memberof WebReverseMapServiceImageryProvider.prototype
+ * @type {TileDiscardPolicy}
+ * @readonly
+ */
+ get tileDiscardPolicy() {
+ return this._tileProvider.tileDiscardPolicy;
+ }
+
+ /**
+ * Gets an event that is raised when the imagery provider encounters an asynchronous error. By subscribing
+ * to the event, you will be notified of the error and can potentially recover from it. Event listeners
+ * are passed an instance of {@link TileProviderError}.
+ * @memberof WebReverseMapServiceImageryProvider.prototype
+ * @type {Event}
+ * @readonly
+ */
+
+ get errorEvent() {
+ return this._tileProvider.errorEvent;
+ }
+
+ /**
+ * Gets a value indicating whether or not the provider is ready for use.
+ * @memberof WebReverseMapServiceImageryProvider.prototype
+ * @type {Boolean}
+ * @readonly
+ */
+
+ get ready() {
+ return this._tileProvider.ready;
+ }
+
+ /**
+ * Gets a promise that resolves to true when the provider is ready for use.
+ * @memberof WebReverseMapServiceImageryProvider.prototype
+ * @type {Promise.}
+ * @readonly
+ */
+
+ get readyPromise() {
+ return this._tileProvider.readyPromise;
+ }
+
+ /**
+ * Gets the credit to display when this imagery provider is active. Typically this is used to credit
+ * the source of the imagery. This function should not be called before {@link WebReverseMapServiceImageryProvider#ready} returns true.
+ * @memberof WebReverseMapServiceImageryProvider.prototype
+ * @type {Credit}
+ * @readonly
+ */
+
+ get credit() {
+ return this._tileProvider.credit;
+ }
+
+ /**
+ * Gets a value indicating whether or not the images provided by this imagery provider
+ * include an alpha channel. If this property is false, an alpha channel, if present, will
+ * be ignored. If this property is true, any images without an alpha channel will be treated
+ * as if their alpha is 1.0 everywhere. When this property is false, memory usage
+ * and texture upload time are reduced.
+ * @memberof WebReverseMapServiceImageryProvider.prototype
+ * @type {Boolean}
+ * @readonly
+ */
+
+ get hasAlphaChannel() {
+ return this._tileProvider.hasAlphaChannel;
+ }
+
+ /**
+ * Gets or sets a value indicating whether feature picking is enabled. If true, {@link WebReverseMapServiceImageryProvider#pickFeatures} will
+ * invoke the GetFeatureInfo
service on the WMS server and attempt to interpret the features included in the response. If false,
+ * {@link WebReverseMapServiceImageryProvider#pickFeatures} will immediately return undefined (indicating no pickable
+ * features) without communicating with the server. Set this property to false if you know your data
+ * source does not support picking features or if you don't want this provider's features to be pickable.
+ * @memberof WebReverseMapServiceImageryProvider.prototype
+ * @type {Boolean}
+ * @default true
+ */
+
+ get enablePickFeatures() {
+ return this._tileProvider.enablePickFeatures;
+ }
+ set enablePickFeatures(enablePickFeatures) {
+ this._tileProvider.enablePickFeatures = enablePickFeatures;
+ }
+}
+
+function objectToLowercase(obj) {
+ var result = {};
+ for (var key in obj) {
+ if (obj.hasOwnProperty(key)) {
+ result[key.toLowerCase()] = obj[key];
+ }
+ }
+ return result;
+}
+
+export default WebReverseMapServiceImageryProvider;
+CesiumZondy.Provider.WebReverseMapServiceImageryProvider = WebReverseMapServiceImageryProvider;
diff --git a/src/cesiumjs/provider/WmtsTileProvider.js b/src/cesiumjs/provider/WmtsTileProvider.js
index 789aeabdf..feeb7e904 100644
--- a/src/cesiumjs/provider/WmtsTileProvider.js
+++ b/src/cesiumjs/provider/WmtsTileProvider.js
@@ -1,4 +1,4 @@
-import Cesium from "../../../node_modules/cesium/Source/Cesium";
+// import Cesium from "../../../node_modules/cesium/Source/Cesium";
import { CesiumZondy } from "../core/Base";
var defaultCreditZondy = new Cesium.Credit("MapGISMap");
diff --git a/src/cesiumjs/provider/index.js b/src/cesiumjs/provider/index.js
index 60f768d7f..0e536df65 100644
--- a/src/cesiumjs/provider/index.js
+++ b/src/cesiumjs/provider/index.js
@@ -1,3 +1,4 @@
-export { IgsTileProvider } from "./IgsTileProvider";
-export { IgsDocProvider } from "./IgsDocProvider";
-export { WmtsTileProvider } from "./WmtsTileProvider";
+export { IgsTileProvider } from './IgsTileProvider';
+export { IgsDocProvider } from './IgsDocProvider';
+export { WmtsTileProvider } from './WmtsTileProvider';
+export { WebReverseMapServiceImageryProvider } from './WebReverseMapServiceImageryProvider';
diff --git a/src/cesiumjs/render/VectorTileLayer.js b/src/cesiumjs/render/VectorTileLayer.js
index ee5e749e4..627539e07 100644
--- a/src/cesiumjs/render/VectorTileLayer.js
+++ b/src/cesiumjs/render/VectorTileLayer.js
@@ -15,17 +15,18 @@ import axios from 'axios';
* @param {String} [option.ip = localhost] 地图服务ip
* @param {String} [option.port = 6163] 地图服务port
* @param {String} [option.layerName] 地图名
- * @param {String} option.style 样式json文件路径或者MVT-JSON对象,当为url时等于styleUrl;当为vectortilejson等于vectortilejson
+ * @param {String} option.mvtStyle 样式json文件路径或者MVT-JSON对象,当为url时等于styleUrl;当为vectortilejson等于vectortilejson
* @param {String} [option.styleUrl] 样式json文件路径,有styleUrl就可以直接读取styleUrl里的信息;不然就是加载中地发布的矢量瓦片,使用ip,port和layerName先拼接styleUrl路径再进行查询。
* @param {Object} [option.vectortilejson] 矢量瓦片json对象,直接取json对象,不需要再去请求。
* @param {Cesium.TilingScheme} [option.tilingScheme] 矢量瓦片瓦片切分规则:经纬度还是墨卡托
* @param {String} [option.token] 第三方需要的token,比如mapbox
* @param {String} [option.show=true] 是否可见
+ * @param {String} [option.callback] 加载矢量瓦片成功回调,返回Provider
* @example
* vectortileLayer = new CesiumZondy.Overlayer.VectorTileLayer(
webGlobe.viewer,
{
- style:"http://develop.smaryun.com:6163/igs/rest/mrms/vtiles/styles/街道-墨卡托.json",
+ mvtStyle:"http://develop.smaryun.com:6163/igs/rest/mrms/vtiles/styles/街道-墨卡托.json",
token: "",
show: true,
}
@@ -42,31 +43,30 @@ export class VectorTileLayer {
}
this.options = options;
+ this.callback = options.callback;
this.token = options.token || '';
this.opacity = options.opacity || 1;
this.vectortilejson = options.vectortilejson;
this.threadId = options.threadId || Math.random() * 10000;
this.show = options.show;
- this.style = options.style;
+ this.mvtStyle = options.mvtStyle;
this.styleUrl = options.styleUrl;
this.tilingScheme = options.tilingScheme;
this.provider = null;
- console.log(options, this);
-
this.initDevicePixelRatio();
//this.bindEvent();
-
- if (this.style) {
- if (this.style.indexOf('http') >= 0) {
+
+ if (this.mvtStyle) {
+ if (typeof this.mvtStyle === 'string') {
//如果是个网络地址,就通过url请求获取矢量瓦片json对象
- this.url = this.style;
+ this.url = this.mvtStyle;
this.requestVectortileJson();
} else {
- this.requestStyleData();
+ this.requestStyleData(this.mvtStyle);
}
} else if (this.styleUrl) {
- if (this.styleUrl.indexOf('http') >= 0) {
+ if (typeof this.styleUrl === 'string') {
this.url = this.styleUrl;
this.requestVectortileJson();
} else {
@@ -77,7 +77,7 @@ export class VectorTileLayer {
//如果没有矢量瓦片json对象,就通过url请求获取矢量瓦片json对象
this.requestVectortileJson();
} else {
- this.requestStyleData();
+ this.requestStyleData(this.vectortilejson);
}
}
}
@@ -117,6 +117,7 @@ export class VectorTileLayer {
* @see https://docs.mapbox.com/mapbox-gl-js/style-spec/
*/
requestStyleData(vectortilejson) {
+ this.vectortilejson = vectortilejson;
var layers = getLayers(vectortilejson);
var sources = getVectorTileSource(vectortilejson);
var spritepng = getSpritePng(vectortilejson);
@@ -136,6 +137,10 @@ export class VectorTileLayer {
);
}
+ getLayer() {
+ return self.provider ? self.provider : undefined;
+ }
+
/**
* 首先构造矢量瓦片样式,再添加图层
* @function module:客户端渲染.VectorTileLayer.prototype.addLayer
@@ -165,38 +170,86 @@ export class VectorTileLayer {
});
this.provider = this.viewer.imageryLayers.addImageryProvider(vectortile);
this.provider.show = this.show;
+
+ if (this.callback) {
+ this.callback({ imageryLayer: this.provider });
+ }
}
}
/**
- * 通过修改图层样式,更新图层
- * @function module:客户端渲染.VectorTileLayer.prototype.updateLayer
- * @param {Array} layersStyle 所有图层的样式参数 Array
+ * @description 设置布局属性
+ * @function module:客户端渲染.VectorTileLayer.prototype.updateStyle
+ * @param {Object} mvtStyle
*/
- updateLayer(layersStyle) {
- if (!this.styleData || !this.styleData.vectortilejson || !this.styleData.vectortilejson.layers) {
- return;
- }
+ updateStyle(mvtStyle) {
+ if (!this.styleData) return;
+ this.styleData.vectortilejson = mvtStyle;
this.remove();
- this.styleData.vectortilejson.layers = layersStyle;
- var layers = [];
- for (var i = 0; i < layersStyle.length; i++) {
- layers.push(layersStyle[i].id);
- }
- this.styleData.layers = layers;
this.addLayer(this.styleData);
}
/**
- * 获取所有图层的样式
- * @function module:客户端渲染.VectorTileLayer.prototype.getLayersStyle
- * @returns {*} 获取满足MVT样式的图层信息
+ * @description 设置布局属性
+ * @function module:客户端渲染.VectorTileLayer.prototype.setLayoutProperty
+ * @param {String} layer
+ * @param {String} key
+ * @param {Object} value
*/
- getLayersStyle() {
- if (!this.styleData || !this.styleData.vectortilejson || !this.styleData.vectortilejson.layers) {
- return;
- }
- return this.styleData.vectortilejson.layers;
+ setLayoutProperty(layerId, key, value) {
+ if (!this.vectortilejson || !this.vectortilejson.layers) return;
+ const { layers } = this.vectortilejson;
+ let finds = layers.filter((l) => {
+ return l.id === layerId;
+ });
+ let layer = finds && finds.length > 0 ? finds[0] : undefined;
+ if (!layer) return;
+ layer.layout = layer.layout || {};
+ layer.layout[key] = value;
+ this.styleData.vectortilejson = this.vectortilejson;
+ this.remove();
+ this.addLayer(this.styleData);
+ }
+
+ /**
+ * @description 设置画笔属性
+ * @function module:客户端渲染.VectorTileLayer.prototype.setPaintProperty
+ * @param {String} layerId
+ * @param {String} key
+ * @param {Object} value
+ */
+ setPaintProperty(layerId, key, value) {
+ if (!this.vectortilejson || !this.vectortilejson.layers) return;
+ const { layers } = this.vectortilejson;
+ let finds = layers.filter((l) => {
+ return l.id === layerId;
+ });
+ let layer = finds && finds.length > 0 ? finds[0] : undefined;
+ if (!layer) return;
+ layer.paint = layer.paint || {};
+ layer.paint[key] = value;
+ this.remove();
+ this.addLayer(this.styleData);
+ }
+
+ /**
+ * @description 设置过滤属性
+ * @function module:客户端渲染.VectorTileLayer.prototype.setFilter
+ * @param {String} layerId
+ * @param {Array} rule
+ */
+ setFilter(layerId, rule) {
+ if (!this.vectortilejson || !this.vectortilejson.layers) return;
+ const { layers } = this.vectortilejson;
+ let finds = layers.filter((l) => {
+ return l.id === layerId;
+ });
+ let layer = finds && finds.length > 0 ? finds[0] : undefined;
+ if (!layer) return;
+ layer.filter = layer.filter || {};
+ layer.filter = rule;
+ this.remove();
+ this.addLayer(this.styleData);
}
unbindEvent() {}
@@ -233,12 +286,17 @@ export class VectorTileLayer {
*/
remove() {
let self = this;
+ if (self.provider) {
+ self.viewer.imageryLayers.remove(self.provider, true);
+ self.provider.show = false;
+ }
+ /* let self = this;
window.setTimeout(() => {
if (self.provider) {
self.viewer.imageryLayers.remove(self.provider, true);
self.provider.show = false;
}
- }, 1000);
+ }, 1000); */
}
}
diff --git a/src/cesiumjs/render/vectortile/MapgisVectorTileStyle.js b/src/cesiumjs/render/vectortile/MapgisVectorTileStyle.js
index c4f2fa66c..a2c058eaf 100644
--- a/src/cesiumjs/render/vectortile/MapgisVectorTileStyle.js
+++ b/src/cesiumjs/render/vectortile/MapgisVectorTileStyle.js
@@ -108,7 +108,7 @@ export function getValue(layer, layoutOrPaint, property, zoom, feature) {
const filterCache = {};
function evaluateFilter(layerId, filter, feature, zoom) {
if (!(layerId in filterCache)) {
- filterCache[layerId] = createFilter(filter);
+ filterCache[layerId] = createFilter(filter).filter;;
}
zoomObj.zoom = zoom;
return filterCache[layerId](zoomObj, feature);
@@ -324,18 +324,56 @@ export default function(glStyle, source, resolutions, spriteData, spriteImageUrl
if (layout.visibility === 'none' || ('minzoom' in layer && zoom < layer.minzoom) ||
('maxzoom' in layer && zoom >= layer.maxzoom)) {
continue;
- }
- const filter = layer.filter;
- if (!filter || evaluateFilter(layerId, filter, f, zoom)) {
- let color, opacity, fill, stroke, strokeColor, style;
- const index = layerData.index;
- if (type == 3 && layer.type == 'fill') {
- opacity = getValue(layer, 'paint', 'fill-opacity', zoom, f);
- if ('fill-pattern' in paint) {
- const fillIcon = getValue(layer, 'paint', 'fill-pattern', zoom, f);
- if (fillIcon) {
- const icon = fromTemplate(fillIcon, properties);
- if (spriteImage && spriteData && spriteData[icon]) {
+ } else {
+ const filter = layer.filter;
+ if (!filter || evaluateFilter(layerId, filter, f, zoom)) {
+ let color, opacity, fill, stroke, strokeColor, style;
+ const index = layerData.index;
+ if (type == 3 && layer.type == 'fill') {
+ opacity = getValue(layer, 'paint', 'fill-opacity', zoom, f);
+ if ('fill-pattern' in paint) {
+ const fillIcon = getValue(layer, 'paint', 'fill-pattern', zoom, f);
+ if (fillIcon) {
+ const icon = fromTemplate(fillIcon, properties);
+ if (spriteImage && spriteData && spriteData[icon]) {
+ ++stylesLength;
+ style = styles[stylesLength];
+ if (!style || !style.getFill() || style.getStroke() || style.getText()) {
+ style = styles[stylesLength] = new Style({
+ fill: new Fill()
+ });
+ }
+ fill = style.getFill();
+ style.setZIndex(index);
+ const icon_cache_key = icon + '.' + opacity;
+ let pattern = patternCache[icon_cache_key];
+ if (!pattern) {
+ const spriteImageData = spriteData[icon];
+ const canvas = document.createElement('canvas');
+ canvas.width = spriteImageData.width;
+ canvas.height = spriteImageData.height;
+ const ctx = canvas.getContext('2d');
+ ctx.globalAlpha = opacity;
+ ctx.drawImage(
+ spriteImage,
+ spriteImageData.x,
+ spriteImageData.y,
+ spriteImageData.width,
+ spriteImageData.height,
+ 0,
+ 0,
+ spriteImageData.width,
+ spriteImageData.height
+ );
+ pattern = ctx.createPattern(canvas, 'repeat');
+ patternCache[icon_cache_key] = pattern;
+ }
+ fill.setColor(pattern);
+ }
+ }
+ } else {
+ color = colorWithOpacity(getValue(layer, 'paint', 'fill-color', zoom, f), opacity);
+ if (color) {
++stylesLength;
style = styles[stylesLength];
if (!style || !style.getFill() || style.getStroke() || style.getText()) {
@@ -344,51 +382,37 @@ export default function(glStyle, source, resolutions, spriteData, spriteImageUrl
});
}
fill = style.getFill();
+ fill.setColor(color);
style.setZIndex(index);
- const icon_cache_key = icon + '.' + opacity;
- let pattern = patternCache[icon_cache_key];
- if (!pattern) {
- const spriteImageData = spriteData[icon];
- const canvas = document.createElement('canvas');
- canvas.width = spriteImageData.width;
- canvas.height = spriteImageData.height;
- const ctx = canvas.getContext('2d');
- ctx.globalAlpha = opacity;
- ctx.drawImage(
- spriteImage,
- spriteImageData.x,
- spriteImageData.y,
- spriteImageData.width,
- spriteImageData.height,
- 0,
- 0,
- spriteImageData.width,
- spriteImageData.height
- );
- pattern = ctx.createPattern(canvas, 'repeat');
- patternCache[icon_cache_key] = pattern;
- }
- fill.setColor(pattern);
}
- }
- } else {
- color = colorWithOpacity(getValue(layer, 'paint', 'fill-color', zoom, f), opacity);
- if (color) {
- ++stylesLength;
- style = styles[stylesLength];
- if (!style || !style.getFill() || style.getStroke() || style.getText()) {
- style = styles[stylesLength] = new Style({
- fill: new Fill()
- });
+ if ('fill-outline-color' in paint) {
+ strokeColor = colorWithOpacity(getValue(layer, 'paint', 'fill-outline-color', zoom, f), opacity);
+ }
+ if (strokeColor) {
+ ++stylesLength;
+ style = styles[stylesLength];
+ if (!style || !style.getStroke() || style.getFill() || style.getText()) {
+ style = styles[stylesLength] = new Style({
+ stroke: new Stroke()
+ });
+ }
+ stroke = style.getStroke();
+ stroke.setLineCap(spec['layout_line']['line-cap']);
+ stroke.setLineJoin(spec['layout_line']['line-join']);
+ stroke.setMiterLimit(spec['layout_line']['line-miter-limit']);
+ stroke.setColor(strokeColor);
+ stroke.setWidth(1);
+ stroke.setLineDash(null);
+ style.setZIndex(index);
}
- fill = style.getFill();
- fill.setColor(color);
- style.setZIndex(index);
- }
- if ('fill-outline-color' in paint) {
- strokeColor = colorWithOpacity(getValue(layer, 'paint', 'fill-outline-color', zoom, f), opacity);
}
- if (strokeColor) {
+ }
+ if (type != 1 && layer.type == 'line') {
+ color = !('line-pattern' in paint) && 'line-color' in paint ?
+ colorWithOpacity(getValue(layer, 'paint', 'line-color', zoom, f), getValue(layer, 'paint', 'line-opacity', zoom, f)) :
+ undefined;
+ const width = getValue(layer, 'paint', 'line-width', zoom, f);
+ if (color && width > 0) {
++stylesLength;
style = styles[stylesLength];
if (!style || !style.getStroke() || style.getFill() || style.getText()) {
@@ -397,270 +421,245 @@ export default function(glStyle, source, resolutions, spriteData, spriteImageUrl
});
}
stroke = style.getStroke();
- stroke.setLineCap(spec['layout_line']['line-cap']);
- stroke.setLineJoin(spec['layout_line']['line-join']);
- stroke.setMiterLimit(spec['layout_line']['line-miter-limit']);
- stroke.setColor(strokeColor);
- stroke.setWidth(1);
- stroke.setLineDash(null);
+ stroke.setLineCap(getValue(layer, 'layout', 'line-cap', zoom, f));
+ stroke.setLineJoin(getValue(layer, 'layout', 'line-join', zoom, f));
+ stroke.setMiterLimit(getValue(layer, 'layout', 'line-miter-limit', zoom, f));
+ stroke.setColor(color);
+ stroke.setWidth(width);
+ stroke.setLineDash(paint['line-dasharray'] ?
+ getValue(layer, 'paint', 'line-dasharray', zoom, f).map(function(x) {
+ return x * width;
+ }) : null);
style.setZIndex(index);
}
}
- }
- if (type != 1 && layer.type == 'line') {
- //if(global_log) console.log("styleFunction 2");
- color = !('line-pattern' in paint) && 'line-color' in paint ?
- colorWithOpacity(getValue(layer, 'paint', 'line-color', zoom, f), getValue(layer, 'paint', 'line-opacity', zoom, f)) :
- undefined;
- const width = getValue(layer, 'paint', 'line-width', zoom, f);
- if (color && width > 0) {
- ++stylesLength;
- style = styles[stylesLength];
- if (!style || !style.getStroke() || style.getFill() || style.getText()) {
- style = styles[stylesLength] = new Style({
- stroke: new Stroke()
- });
- }
- stroke = style.getStroke();
- stroke.setLineCap(getValue(layer, 'layout', 'line-cap', zoom, f));
- stroke.setLineJoin(getValue(layer, 'layout', 'line-join', zoom, f));
- stroke.setMiterLimit(getValue(layer, 'layout', 'line-miter-limit', zoom, f));
- stroke.setColor(color);
- stroke.setWidth(width);
- stroke.setLineDash(paint['line-dasharray'] ?
- getValue(layer, 'paint', 'line-dasharray', zoom, f).map(function(x) {
- return x * width;
- }) : null);
- style.setZIndex(index);
- }
- }
-
- let hasImage = false;
- let text = null;
- let placementAngle = 0;
- let icon, iconImg, skipLabel;
- if ((type == 1 || type == 2) && 'icon-image' in layout) {
- const iconImage = getValue(layer, 'layout', 'icon-image', zoom, f);
- if (iconImage) {
- icon = fromTemplate(iconImage, properties);
- let styleGeom = undefined;
- if (spriteImage && spriteData && spriteData[icon]) {
- const iconRotationAlignment = getValue(layer, 'layout', 'icon-rotation-alignment', zoom, f);
- if (type == 2) {
- const geom = feature.getGeometry();
- // ol package and ol-debug.js only
- if (geom.getFlatMidpoint) {
- const extent = geom.getExtent();
- const size = Math.sqrt(Math.max(
- Math.pow((extent[2] - extent[0]) / resolution, 2),
- Math.pow((extent[3] - extent[1]) / resolution, 2))
- );
- if (size > 150) {
- //FIXME Do not hard-code a size of 150
- const midpoint = geom.getFlatMidpoint();
- styleGeom = new Point(midpoint);
- const placement = getValue(layer, 'layout', 'symbol-placement', zoom, f);
- if (placement === 'line' && iconRotationAlignment === 'map') {
- const stride = geom.getStride();
- const coordinates = geom.getFlatCoordinates();
- for (let i = 0, ii = coordinates.length - stride; i < ii; i += stride) {
- const x1 = coordinates[i];
- const y1 = coordinates[i + 1];
- const x2 = coordinates[i + stride];
- const y2 = coordinates[i + stride + 1];
- const minX = Math.min(x1, x2);
- const minY = Math.min(y1, y2);
- const maxX = Math.max(x1, x2);
- const maxY = Math.max(y1, y2);
- if (midpoint[0] >= minX && midpoint[0] <= maxX &&
- midpoint[1] >= minY && midpoint[1] <= maxY) {
- placementAngle = Math.atan2(y1 - y2, x2 - x1);
- break;
+
+ let hasImage = false;
+ let text = null;
+ let placementAngle = 0;
+ let icon, iconImg, skipLabel;
+ if ((type == 1 || type == 2) && 'icon-image' in layout) {
+ const iconImage = getValue(layer, 'layout', 'icon-image', zoom, f);
+ if (iconImage) {
+ icon = fromTemplate(iconImage, properties);
+ let styleGeom = undefined;
+ if (spriteImage && spriteData && spriteData[icon]) {
+ const iconRotationAlignment = getValue(layer, 'layout', 'icon-rotation-alignment', zoom, f);
+ if (type == 2) {
+ const geom = feature.getGeometry();
+ // ol package and ol-debug.js only
+ if (geom.getFlatMidpoint) {
+ const extent = geom.getExtent();
+ const size = Math.sqrt(Math.max(
+ Math.pow((extent[2] - extent[0]) / resolution, 2),
+ Math.pow((extent[3] - extent[1]) / resolution, 2))
+ );
+ if (size > 150) {
+ //FIXME Do not hard-code a size of 150
+ const midpoint = geom.getFlatMidpoint();
+ styleGeom = new Point(midpoint);
+ const placement = getValue(layer, 'layout', 'symbol-placement', zoom, f);
+ if (placement === 'line' && iconRotationAlignment === 'map') {
+ const stride = geom.getStride();
+ const coordinates = geom.getFlatCoordinates();
+ for (let i = 0, ii = coordinates.length - stride; i < ii; i += stride) {
+ const x1 = coordinates[i];
+ const y1 = coordinates[i + 1];
+ const x2 = coordinates[i + stride];
+ const y2 = coordinates[i + stride + 1];
+ const minX = Math.min(x1, x2);
+ const minY = Math.min(y1, y2);
+ const maxX = Math.max(x1, x2);
+ const maxY = Math.max(y1, y2);
+ if (midpoint[0] >= minX && midpoint[0] <= maxX &&
+ midpoint[1] >= minY && midpoint[1] <= maxY) {
+ placementAngle = Math.atan2(y1 - y2, x2 - x1);
+ break;
+ }
}
}
}
}
}
- }
- if (type !== 2 || styleGeom) {
- ++stylesLength;
- style = styles[stylesLength];
- if (!style || !style.getImage() || style.getFill() || style.getStroke()) {
- style = styles[stylesLength] = new Style();
- }
- style.setGeometry(styleGeom);
- const iconSize = getValue(layer, 'layout', 'icon-size', zoom, f);
- const iconColor = paint['icon-color'] !== undefined ? getValue(layer, 'paint', 'icon-color', zoom, f) : null;
- let icon_cache_key = icon + '.' + iconSize;
- if (iconColor !== null) {
- icon_cache_key += '.' + iconColor;
- }
- iconImg = iconImageCache[icon_cache_key];
- if (!iconImg) {
- const spriteImageData = spriteData[icon];
+ if (type !== 2 || styleGeom) {
+ ++stylesLength;
+ style = styles[stylesLength];
+ if (!style || !style.getImage() || style.getFill() || style.getStroke()) {
+ style = styles[stylesLength] = new Style();
+ }
+ style.setGeometry(styleGeom);
+ const iconSize = getValue(layer, 'layout', 'icon-size', zoom, f);
+ const iconColor = paint['icon-color'] !== undefined ? getValue(layer, 'paint', 'icon-color', zoom, f) : null;
+ let icon_cache_key = icon + '.' + iconSize;
if (iconColor !== null) {
- // cut out the sprite and color it
- color = colorWithOpacity(iconColor, 1);
- const canvas = document.createElement('canvas');
- canvas.width = spriteImageData.width;
- canvas.height = spriteImageData.height;
- const ctx = canvas.getContext('2d');
- ctx.drawImage(
- spriteImage,
- spriteImageData.x,
- spriteImageData.y,
- spriteImageData.width,
- spriteImageData.height,
- 0,
- 0,
- spriteImageData.width,
- spriteImageData.height
- );
- const data = ctx.getImageData(0, 0, canvas.width, canvas.height);
- for (let c = 0, cc = data.data.length; c < cc; c += 4) {
- data.data[c] = color[0];
- data.data[c + 1] = color[1];
- data.data[c + 2] = color[2];
+ icon_cache_key += '.' + iconColor;
+ }
+ iconImg = iconImageCache[icon_cache_key];
+ if (!iconImg) {
+ const spriteImageData = spriteData[icon];
+ if (iconColor !== null) {
+ // cut out the sprite and color it
+ color = colorWithOpacity(iconColor, 1);
+ const canvas = document.createElement('canvas');
+ canvas.width = spriteImageData.width;
+ canvas.height = spriteImageData.height;
+ const ctx = canvas.getContext('2d');
+ ctx.drawImage(
+ spriteImage,
+ spriteImageData.x,
+ spriteImageData.y,
+ spriteImageData.width,
+ spriteImageData.height,
+ 0,
+ 0,
+ spriteImageData.width,
+ spriteImageData.height
+ );
+ const data = ctx.getImageData(0, 0, canvas.width, canvas.height);
+ for (let c = 0, cc = data.data.length; c < cc; c += 4) {
+ data.data[c] = color[0];
+ data.data[c + 1] = color[1];
+ data.data[c + 2] = color[2];
+ }
+ ctx.putImageData(data, 0, 0);
+ iconImg = iconImageCache[icon_cache_key] = new Icon({
+ img: canvas,
+ imgSize: [canvas.width, canvas.height],
+ scale: iconSize / spriteImageData.pixelRatio
+ });
+ } else {
+ iconImg = iconImageCache[icon_cache_key] = new Icon({
+ img: spriteImage,
+ imgSize: spriteImgSize,
+ size: [spriteImageData.width, spriteImageData.height],
+ offset: [spriteImageData.x, spriteImageData.y],
+ rotateWithView: iconRotationAlignment === 'map',
+ scale: iconSize / spriteImageData.pixelRatio
+ });
}
- ctx.putImageData(data, 0, 0);
- iconImg = iconImageCache[icon_cache_key] = new Icon({
- img: canvas,
- imgSize: [canvas.width, canvas.height],
- scale: iconSize / spriteImageData.pixelRatio
- });
- } else {
- iconImg = iconImageCache[icon_cache_key] = new Icon({
- img: spriteImage,
- imgSize: spriteImgSize,
- size: [spriteImageData.width, spriteImageData.height],
- offset: [spriteImageData.x, spriteImageData.y],
- rotateWithView: iconRotationAlignment === 'map',
- scale: iconSize / spriteImageData.pixelRatio
- });
}
+ iconImg.setRotation(placementAngle + deg2rad(getValue(layer, 'layout', 'icon-rotate', zoom, f)));
+ iconImg.setOpacity(getValue(layer, 'paint', 'icon-opacity', zoom, f));
+ style.setImage(iconImg);
+ text = style.getText();
+ style.setText(undefined);
+ style.setZIndex(99999 - index);
+ hasImage = true;
+ skipLabel = false;
+ } else {
+ skipLabel = true;
}
- iconImg.setRotation(placementAngle + deg2rad(getValue(layer, 'layout', 'icon-rotate', zoom, f)));
- iconImg.setOpacity(getValue(layer, 'paint', 'icon-opacity', zoom, f));
- style.setImage(iconImg);
- text = style.getText();
- style.setText(undefined);
- style.setZIndex(99999 - index);
- hasImage = true;
- skipLabel = false;
- } else {
- skipLabel = true;
}
}
}
- }
- if (type == 1 && 'circle-radius' in paint) {
- ++stylesLength;
- style = styles[stylesLength];
- if (!style || !style.getImage() || style.getFill() || style.getStroke()) {
- style = styles[stylesLength] = new Style();
- }
- const circleRadius = getValue(layer, 'paint', 'circle-radius', zoom, f);
- const circleStrokeColor = getValue(layer, 'paint', 'circle-stroke-color', zoom, f);
- const circleColor = getValue(layer, 'paint', 'circle-color', zoom, f);
- const circleOpacity = getValue(layer, 'paint', 'circle-opacity', zoom, f);
- const circleStrokeOpacity = getValue(layer, 'paint', 'circle-stroke-opacity', zoom, f);
- const circleStrokeWidth = getValue(layer, 'paint', 'circle-stroke-width', zoom, f);
- const cache_key = circleRadius + '.' + circleStrokeColor + '.' +
- circleColor + '.' + circleOpacity + '.' + circleStrokeWidth;
- iconImg = iconImageCache[cache_key];
- if (!iconImg) {
- iconImg = new Circle({
- radius: circleRadius,
- stroke: circleStrokeWidth === 0 ? undefined : new Stroke({
- width: circleStrokeWidth,
- color: colorWithOpacity(circleStrokeColor, circleStrokeOpacity)
- }),
- fill: new Fill({
- color: colorWithOpacity(circleColor, circleOpacity)
- })
- });
- }
- style.setImage(iconImg);
- text = style.getText();
- style.setText(undefined);
- style.setGeometry(undefined);
- style.setZIndex(99999 - index);
- hasImage = true;
- }
-
- let label;
- if ('text-field' in layout) {
- const textField = getValue(layer, 'layout', 'text-field', zoom, f).toString();
- label = fromTemplate(textField, properties);
- }
- if (label && !skipLabel) {
- if (!hasImage) {
- ++stylesLength;
+ if (type == 1 && 'circle-radius' in paint) {
+ ++stylesLength;
style = styles[stylesLength];
- if (!style || !style.getText() || style.getFill() || style.getStroke()) {
+ if (!style || !style.getImage() || style.getFill() || style.getStroke()) {
style = styles[stylesLength] = new Style();
}
- style.setImage(undefined);
+ const circleRadius = getValue(layer, 'paint', 'circle-radius', zoom, f);
+ const circleStrokeColor = getValue(layer, 'paint', 'circle-stroke-color', zoom, f);
+ const circleColor = getValue(layer, 'paint', 'circle-color', zoom, f);
+ const circleOpacity = getValue(layer, 'paint', 'circle-opacity', zoom, f);
+ const circleStrokeOpacity = getValue(layer, 'paint', 'circle-stroke-opacity', zoom, f);
+ const circleStrokeWidth = getValue(layer, 'paint', 'circle-stroke-width', zoom, f);
+ const cache_key = circleRadius + '.' + circleStrokeColor + '.' +
+ circleColor + '.' + circleOpacity + '.' + circleStrokeWidth;
+ iconImg = iconImageCache[cache_key];
+ if (!iconImg) {
+ iconImg = new Circle({
+ radius: circleRadius,
+ stroke: circleStrokeWidth === 0 ? undefined : new Stroke({
+ width: circleStrokeWidth,
+ color: colorWithOpacity(circleStrokeColor, circleStrokeOpacity)
+ }),
+ fill: new Fill({
+ color: colorWithOpacity(circleColor, circleOpacity)
+ })
+ });
+ }
+ style.setImage(iconImg);
+ text = style.getText();
+ style.setText(undefined);
style.setGeometry(undefined);
+ style.setZIndex(99999 - index);
+ hasImage = true;
}
- if (!style.getText()) {
- style.setText(text || new Text());
- }
- text = style.getText();
- const textSize = getValue(layer, 'layout', 'text-size', zoom, f);
- const fontArray = getValue(layer, 'layout', 'text-font', zoom, f);
-
- //if(properties.layer == "周边国家首都") console.log("symbol", getFonts);
- const font = mapboxToCssFont( /* getFonts ? getFonts(fontArray) : */fontArray, textSize);
-
- const textTransform = layout['text-transform'];
- if (textTransform == 'uppercase') {
- label = label.toUpperCase();
- } else if (textTransform == 'lowercase') {
- label = label.toLowerCase();
+
+ let label;
+ if ('text-field' in layout) {
+ const textField = getValue(layer, 'layout', 'text-field', zoom, f).toString();
+ label = fromTemplate(textField, properties);
}
- const wrappedLabel = type == 2 ? label : wrapText(label, font, getValue(layer, 'layout', 'text-max-width', zoom, f));
- text.setText(wrappedLabel);
- text.setFont(font);
- text.setRotation(deg2rad(getValue(layer, 'layout', 'text-rotate', zoom, f)));
- const textAnchor = getValue(layer, 'layout', 'text-anchor', zoom, f);
- const placement = (hasImage || type == 1) ? 'point' : getValue(layer, 'layout', 'symbol-placement', zoom, f);
- text.setPlacement(placement);
- if (placement == 'point') {
- let textAlign = 'center';
- if (textAnchor.indexOf('left') !== -1) {
- textAlign = 'left';
- } else if (textAnchor.indexOf('right') !== -1) {
- textAlign = 'right';
+ if (label && !skipLabel) {
+ if (!hasImage) {
+ ++stylesLength;
+ style = styles[stylesLength];
+ if (!style || !style.getText() || style.getFill() || style.getStroke()) {
+ style = styles[stylesLength] = new Style();
+ }
+ style.setImage(undefined);
+ style.setGeometry(undefined);
}
- text.setTextAlign(textAlign);
- } else {
- text.setTextAlign();
- }
- let textBaseline = 'middle';
- if (textAnchor.indexOf('bottom') == 0) {
- textBaseline = 'bottom';
- } else if (textAnchor.indexOf('top') == 0) {
- textBaseline = 'top';
- }
- text.setTextBaseline(textBaseline);
- const textOffset = getValue(layer, 'layout', 'text-offset', zoom, f);
- text.setOffsetX(textOffset[0] * textSize);
- text.setOffsetY(textOffset[1] * textSize);
- opacity = getValue(layer, 'paint', 'text-opacity', zoom, f);
- textColor.setColor(colorWithOpacity(getValue(layer, 'paint', 'text-color', zoom, f), opacity));
- text.setFill(textColor);
- const haloColor = colorWithOpacity(getValue(layer, 'paint', 'text-halo-color', zoom, f), opacity);
- if (haloColor) {
- textHalo.setColor(haloColor);
- textHalo.setWidth(getValue(layer, 'paint', 'text-halo-width', zoom, f));
- text.setStroke(textHalo);
- } else {
- text.setStroke(undefined);
+ if (!style.getText()) {
+ style.setText(text || new Text());
+ }
+ text = style.getText();
+ const textSize = getValue(layer, 'layout', 'text-size', zoom, f);
+ const fontArray = getValue(layer, 'layout', 'text-font', zoom, f);
+
+ const font = mapboxToCssFont( /* getFonts ? getFonts(fontArray) : */fontArray, textSize);
+
+ const textTransform = layout['text-transform'];
+ if (textTransform == 'uppercase') {
+ label = label.toUpperCase();
+ } else if (textTransform == 'lowercase') {
+ label = label.toLowerCase();
+ }
+ const wrappedLabel = type == 2 ? label : wrapText(label, font, getValue(layer, 'layout', 'text-max-width', zoom, f));
+ text.setText(wrappedLabel);
+ text.setFont(font);
+ text.setRotation(deg2rad(getValue(layer, 'layout', 'text-rotate', zoom, f)));
+ const textAnchor = getValue(layer, 'layout', 'text-anchor', zoom, f);
+ const placement = (hasImage || type == 1) ? 'point' : getValue(layer, 'layout', 'symbol-placement', zoom, f);
+ text.setPlacement(placement);
+ if (placement == 'point') {
+ let textAlign = 'center';
+ if (textAnchor.indexOf('left') !== -1) {
+ textAlign = 'left';
+ } else if (textAnchor.indexOf('right') !== -1) {
+ textAlign = 'right';
+ }
+ text.setTextAlign(textAlign);
+ } else {
+ text.setTextAlign();
+ }
+ let textBaseline = 'middle';
+ if (textAnchor.indexOf('bottom') == 0) {
+ textBaseline = 'bottom';
+ } else if (textAnchor.indexOf('top') == 0) {
+ textBaseline = 'top';
+ }
+ text.setTextBaseline(textBaseline);
+ const textOffset = getValue(layer, 'layout', 'text-offset', zoom, f);
+ text.setOffsetX(textOffset[0] * textSize);
+ text.setOffsetY(textOffset[1] * textSize);
+ opacity = getValue(layer, 'paint', 'text-opacity', zoom, f);
+ textColor.setColor(colorWithOpacity(getValue(layer, 'paint', 'text-color', zoom, f), opacity));
+ text.setFill(textColor);
+ const haloColor = colorWithOpacity(getValue(layer, 'paint', 'text-halo-color', zoom, f), opacity);
+ if (haloColor) {
+ textHalo.setColor(haloColor);
+ textHalo.setWidth(getValue(layer, 'paint', 'text-halo-width', zoom, f));
+ text.setStroke(textHalo);
+ } else {
+ text.setStroke(undefined);
+ }
+ style.setZIndex(99999 - index);
}
- style.setZIndex(99999 - index);
}
- }
+ }
}
if (stylesLength > -1) {
diff --git a/src/cesiumjs/render/vectortile/MapgisVectorTileUtil.js b/src/cesiumjs/render/vectortile/MapgisVectorTileUtil.js
new file mode 100644
index 000000000..a04ff011c
--- /dev/null
+++ b/src/cesiumjs/render/vectortile/MapgisVectorTileUtil.js
@@ -0,0 +1,126 @@
+import {listen} from 'ol/events';
+import EventType from 'ol/events/EventType';
+import {labelCache} from 'ol/render/canvas';
+
+export function deg2rad(degrees) {
+ return degrees * Math.PI / 180;
+}
+
+export const defaultResolutions = (function() {
+ const resolutions = [];
+ for (let res = 78271.51696402048; resolutions.length <= 24; res /= 2) {
+ resolutions.push(res);
+ }
+ return resolutions;
+})();
+
+export function getZoomForResolution(resolution, resolutions) {
+ let i = 0;
+ const ii = resolutions.length;
+ for (; i < ii; ++i) {
+ const candidate = resolutions[i];
+ if (candidate < resolution && i + 1 < ii) {
+ const zoomFactor = resolutions[i] / resolutions[i + 1];
+ return i + Math.log(resolutions[i] / resolution) / Math.log(zoomFactor);
+ }
+ }
+ return ii - 1;
+}
+
+const hairSpacePool = Array(256).join('\u200A');
+export function applyLetterSpacing(text, letterSpacing) {
+ if (letterSpacing >= 0.05) {
+ let textWithLetterSpacing = '';
+ const lines = text.split('\n');
+ const joinSpaceString = hairSpacePool.slice(0, Math.round(letterSpacing / 0.1));
+ for (let l = 0, ll = lines.length; l < ll; ++l) {
+ if (l > 0) {
+ textWithLetterSpacing += '\n';
+ }
+ textWithLetterSpacing += lines[l].split('').join(joinSpaceString);
+ }
+ return textWithLetterSpacing;
+ }
+ return text;
+}
+
+const ctx = document.createElement('CANVAS').getContext('2d');
+function measureText(text, letterSpacing) {
+ return ctx.measureText(text).width + (text.length - 1) * letterSpacing;
+}
+
+let measureCache = {};
+if (labelCache) {
+ // Only available when using ES modules
+ listen(labelCache, EventType.CLEAR, function() {
+ measureCache = {};
+ });
+}
+export function wrapText(text, font, em, letterSpacing) {
+ const key = em + ',' + font + ',' + text + ',' + letterSpacing;
+ let wrappedText = measureCache[key];
+ if (!wrappedText) {
+ const words = text.split(' ');
+ if (words.length > 1) {
+ ctx.font = font;
+ const oneEm = ctx.measureText('M').width;
+ const maxWidth = oneEm * em;
+ let line = '';
+ const lines = [];
+ // Pass 1 - wrap lines to not exceed maxWidth
+ for (let i = 0, ii = words.length; i < ii; ++i) {
+ const word = words[i];
+ const testLine = line + (line ? ' ' : '') + word;
+ if (measureText(testLine, letterSpacing) <= maxWidth) {
+ line = testLine;
+ } else {
+ if (line) {
+ lines.push(line);
+ }
+ line = word;
+ }
+ }
+ if (line) {
+ lines.push(line);
+ }
+ // Pass 2 - add lines with a width of less than 30% of maxWidth to the previous or next line
+ for (let i = 0, ii = lines.length; i < ii; ++i) {
+ const line = lines[i];
+ if (measureText(line, letterSpacing) < maxWidth * 0.35) {
+ const prevWidth = i > 0 ? measureText(lines[i - 1], letterSpacing) : Infinity;
+ const nextWidth = i < ii - 1 ? measureText(lines[i + 1], letterSpacing) : Infinity;
+ lines.splice(i, 1);
+ if (prevWidth < nextWidth) {
+ lines[i - 1] += ' ' + line;
+ i -= 1;
+ } else {
+ lines[i] = line + ' ' + lines[i];
+ }
+ ii -= 1;
+ }
+ }
+ // Pass 3 - try to fill 80% of maxWidth for each line
+ for (let i = 0, ii = lines.length - 1; i < ii; ++i) {
+ const line = lines[i];
+ const next = lines[i + 1];
+ if (measureText(line, letterSpacing) > maxWidth * 0.7 &&
+ measureText(next, letterSpacing) < maxWidth * 0.6) {
+ const lineWords = line.split(' ');
+ const lastWord = lineWords.pop();
+ if (measureText(lastWord, letterSpacing) < maxWidth * 0.2) {
+ lines[i] = lineWords.join(' ');
+ lines[i + 1] = lastWord + ' ' + next;
+ }
+ ii -= 1;
+ }
+ }
+ wrappedText = lines.join('\n');
+ } else {
+ wrappedText = text;
+ }
+ wrappedText = applyLetterSpacing(wrappedText, letterSpacing);
+ measureCache[key] = wrappedText;
+ }
+ return wrappedText;
+}
+
diff --git a/src/cesiumjs/ui/interact/CesiumSelectionIndicator.js b/src/cesiumjs/ui/interact/CesiumSelectionIndicator.js
index 0b4b42494..bec59c559 100644
--- a/src/cesiumjs/ui/interact/CesiumSelectionIndicator.js
+++ b/src/cesiumjs/ui/interact/CesiumSelectionIndicator.js
@@ -1,7 +1,7 @@
import {CesiumZondy} from '../../core/Base';
-import Cesium from '../../../../node_modules/cesium/Source/Cesium';
-var Cartesian2 = Cesium.Cartesian2;
-var DeveloperError = Cesium.DeveloperError;
+// import Cesium from '../../../../node_modules/cesium/Source/Cesium';
+// var Cartesian2 = Cesium.Cartesian2;
+// var DeveloperError = Cesium.DeveloperError;
import defined from '../../../../node_modules/cesium/Source/Core/defined';
import EasingFunction from '../../../../node_modules/cesium/Source/Core/EasingFunction';
import knockout from '../../../../node_modules/cesium/Source/ThirdParty/knockout';
diff --git a/src/cesiumjs/ui/query/G3DDocQuery.js b/src/cesiumjs/ui/query/G3DDocQuery.js
index c5a9d2835..311c3a2ed 100644
--- a/src/cesiumjs/ui/query/G3DDocQuery.js
+++ b/src/cesiumjs/ui/query/G3DDocQuery.js
@@ -1,5 +1,5 @@
import { CesiumZondy } from '../../core/Base';
-import axios from 'axios';
+import {IgsServiceBase} from "../../../service/baseserver";
/**
* @author IGServer-邬俊惠
@@ -167,22 +167,29 @@ export class G3DDocQuery {
var postData = null;
if (type && type.toLowerCase() === 'post') {
postData = querystring;
- axios.post(url, postData)
- .then(res => {
- successCallback && successCallback(res.data, res, o.layerIndex);
- })
- .catch(error=>{
- errorCallback && errorCallback(error);
- });
+ let service = new IgsServiceBase(url, {
+ eventListeners: {
+ scope: o,
+ processCompleted: successCallback,
+ processFailed: errorCallback
+ }
+ });
+ service.processAsync({
+ method: "POST",
+ data: postData
+ });
} else {
url = url + "?" + querystring;
- axios.get(url)
- .then(res => {
- successCallback && successCallback(res.data, res, o.layerIndex);
- })
- .catch(error=>{
- errorCallback && errorCallback(error);
- });
+ let service = new IgsServiceBase(url, {
+ eventListeners: {
+ scope: o,
+ processCompleted: successCallback,
+ processFailed: errorCallback
+ }
+ });
+ service.processAsync({
+ method: "GET"
+ });
}
}
}
diff --git a/src/cesiumjs/ui/query/MapDocQuery.js b/src/cesiumjs/ui/query/MapDocQuery.js
index 8754e68f8..ea817527c 100644
--- a/src/cesiumjs/ui/query/MapDocQuery.js
+++ b/src/cesiumjs/ui/query/MapDocQuery.js
@@ -1,5 +1,5 @@
import { CesiumZondy } from '../../core/Base';
-import axios from 'axios';
+import {IgsServiceBase} from "../../../service/baseserver";
/**
* @author 技术支持-何振涛
@@ -165,13 +165,14 @@ export class MapDocQuery {
queryString += '&rule=' + o.rule;
let url = 'http://' + o.ip + ':' + o.port + '/igs/rest/mrfs/docs/' + o.docName + '/' + o.mapIndex + '/' + o.layerID + '/' + queryString;
- axios.get(url)
- .then(res => {
- successCallback && successCallback(res.data, res, o);
- })
- .catch(error=>{
- errorCallback && errorCallback(error);
- });
+ let service = new IgsServiceBase(url, {
+ eventListeners: {
+ scope: o,
+ processCompleted: successCallback,
+ processFailed: errorCallback
+ }
+ });
+ service.processAsync();
}
}
diff --git a/src/common/assets/image/bar.png b/src/common/assets/image/bar.png
new file mode 100644
index 000000000..4730a0f54
Binary files /dev/null and b/src/common/assets/image/bar.png differ
diff --git a/src/common/assets/image/bar3D.png b/src/common/assets/image/bar3D.png
new file mode 100644
index 000000000..1a12ac321
Binary files /dev/null and b/src/common/assets/image/bar3D.png differ
diff --git a/src/common/assets/image/ling.png b/src/common/assets/image/ling.png
new file mode 100644
index 000000000..f8c0c4345
Binary files /dev/null and b/src/common/assets/image/ling.png differ
diff --git a/src/common/assets/image/pie.png b/src/common/assets/image/pie.png
new file mode 100644
index 000000000..e2ffaed17
Binary files /dev/null and b/src/common/assets/image/pie.png differ
diff --git a/src/common/assets/image/point.png b/src/common/assets/image/point.png
new file mode 100644
index 000000000..e13c17c18
Binary files /dev/null and b/src/common/assets/image/point.png differ
diff --git a/src/common/assets/image/ring.png b/src/common/assets/image/ring.png
new file mode 100644
index 000000000..1ca1504f4
Binary files /dev/null and b/src/common/assets/image/ring.png differ
diff --git a/src/common/feature/Feature.js b/src/common/feature/Feature.js
new file mode 100644
index 000000000..b4fbdfd67
--- /dev/null
+++ b/src/common/feature/Feature.js
@@ -0,0 +1,318 @@
+/**
+ * @module ol/Feature
+ */
+import BaseObject, {getChangeEventType} from './Object.js';
+import EventType from './events/EventType.js';
+import {assert} from './asserts.js';
+import {listen, unlistenByKey} from './events.js';
+
+/**
+ * @typedef {typeof Feature|typeof import("./render/Feature.js").default} FeatureClass
+ */
+
+/**
+ * @typedef {Feature|import("./render/Feature.js").default} FeatureLike
+ */
+
+/**
+ * @classdesc
+ * A vector object for geographic features with a geometry and other
+ * attribute properties, similar to the features in vector file formats like
+ * GeoJSON.
+ *
+ * Features can be styled individually with `setStyle`; otherwise they use the
+ * style of their vector layer.
+ *
+ * Note that attribute properties are set as {@link module:ol/Object} properties on
+ * the feature object, so they are observable, and have get/set accessors.
+ *
+ * Typically, a feature has a single geometry property. You can set the
+ * geometry using the `setGeometry` method and get it with `getGeometry`.
+ * It is possible to store more than one geometry on a feature using attribute
+ * properties. By default, the geometry used for rendering is identified by
+ * the property name `geometry`. If you want to use another geometry property
+ * for rendering, use the `setGeometryName` method to change the attribute
+ * property associated with the geometry for the feature. For example:
+ *
+ * ```js
+ *
+ * import Feature from 'ol/Feature';
+ * import Polygon from 'ol/geom/Polygon';
+ * import Point from 'ol/geom/Point';
+ *
+ * var feature = new Feature({
+ * geometry: new Polygon(polyCoords),
+ * labelPoint: new Point(labelCoords),
+ * name: 'My Polygon'
+ * });
+ *
+ * // get the polygon geometry
+ * var poly = feature.getGeometry();
+ *
+ * // Render the feature as a point using the coordinates from labelPoint
+ * feature.setGeometryName('labelPoint');
+ *
+ * // get the point geometry
+ * var point = feature.getGeometry();
+ * ```
+ *
+ * @api
+ * @template {import("./geom/Geometry.js").default} Geometry
+ */
+class Feature extends BaseObject {
+ /**
+ * @param {Geometry|Object=} opt_geometryOrProperties
+ * You may pass a Geometry object directly, or an object literal containing
+ * properties. If you pass an object literal, you may include a Geometry
+ * associated with a `geometry` key.
+ */
+ constructor(opt_geometryOrProperties) {
+ super();
+
+ /**
+ * @private
+ * @type {number|string|undefined}
+ */
+ this.id_ = undefined;
+
+ /**
+ * @type {string}
+ * @private
+ */
+ this.geometryName_ = 'geometry';
+
+ /**
+ * User provided style.
+ * @private
+ * @type {import("./style/Style.js").StyleLike}
+ */
+ this.style_ = null;
+
+ /**
+ * @private
+ * @type {import("./style/Style.js").StyleFunction|undefined}
+ */
+ this.styleFunction_ = undefined;
+
+ /**
+ * @private
+ * @type {?import("./events.js").EventsKey}
+ */
+ this.geometryChangeKey_ = null;
+
+ this.addEventListener(
+ getChangeEventType(this.geometryName_),
+ this.handleGeometryChanged_
+ );
+
+ if (opt_geometryOrProperties) {
+ if (
+ typeof (
+ /** @type {?} */ (opt_geometryOrProperties).getSimplifiedGeometry
+ ) === 'function'
+ ) {
+ const geometry = /** @type {Geometry} */ (opt_geometryOrProperties);
+ this.setGeometry(geometry);
+ } else {
+ /** @type {Object} */
+ const properties = opt_geometryOrProperties;
+ this.setProperties(properties);
+ }
+ }
+ }
+
+ /**
+ * Clone this feature. If the original feature has a geometry it
+ * is also cloned. The feature id is not set in the clone.
+ * @return {Feature} The clone.
+ * @api
+ */
+ clone() {
+ const clone = new Feature(
+ this.hasProperties() ? this.getProperties() : null
+ );
+ clone.setGeometryName(this.getGeometryName());
+ const geometry = this.getGeometry();
+ if (geometry) {
+ clone.setGeometry(geometry.clone());
+ }
+ const style = this.getStyle();
+ if (style) {
+ clone.setStyle(style);
+ }
+ return clone;
+ }
+
+ /**
+ * Get the feature's default geometry. A feature may have any number of named
+ * geometries. The "default" geometry (the one that is rendered by default) is
+ * set when calling {@link module:ol/Feature~Feature#setGeometry}.
+ * @return {Geometry|undefined} The default geometry for the feature.
+ * @api
+ * @observable
+ */
+ getGeometry() {
+ return /** @type {Geometry|undefined} */ (this.get(this.geometryName_));
+ }
+
+ /**
+ * Get the feature identifier. This is a stable identifier for the feature and
+ * is either set when reading data from a remote source or set explicitly by
+ * calling {@link module:ol/Feature~Feature#setId}.
+ * @return {number|string|undefined} Id.
+ * @api
+ */
+ getId() {
+ return this.id_;
+ }
+
+ /**
+ * Get the name of the feature's default geometry. By default, the default
+ * geometry is named `geometry`.
+ * @return {string} Get the property name associated with the default geometry
+ * for this feature.
+ * @api
+ */
+ getGeometryName() {
+ return this.geometryName_;
+ }
+
+ /**
+ * Get the feature's style. Will return what was provided to the
+ * {@link module:ol/Feature~Feature#setStyle} method.
+ * @return {import("./style/Style.js").StyleLike|undefined} The feature style.
+ * @api
+ */
+ getStyle() {
+ return this.style_;
+ }
+
+ /**
+ * Get the feature's style function.
+ * @return {import("./style/Style.js").StyleFunction|undefined} Return a function
+ * representing the current style of this feature.
+ * @api
+ */
+ getStyleFunction() {
+ return this.styleFunction_;
+ }
+
+ /**
+ * @private
+ */
+ handleGeometryChange_() {
+ this.changed();
+ }
+
+ /**
+ * @private
+ */
+ handleGeometryChanged_() {
+ if (this.geometryChangeKey_) {
+ unlistenByKey(this.geometryChangeKey_);
+ this.geometryChangeKey_ = null;
+ }
+ const geometry = this.getGeometry();
+ if (geometry) {
+ this.geometryChangeKey_ = listen(
+ geometry,
+ EventType.CHANGE,
+ this.handleGeometryChange_,
+ this
+ );
+ }
+ this.changed();
+ }
+
+ /**
+ * Set the default geometry for the feature. This will update the property
+ * with the name returned by {@link module:ol/Feature~Feature#getGeometryName}.
+ * @param {Geometry|undefined} geometry The new geometry.
+ * @api
+ * @observable
+ */
+ setGeometry(geometry) {
+ this.set(this.geometryName_, geometry);
+ }
+
+ /**
+ * Set the style for the feature to override the layer style. This can be a
+ * single style object, an array of styles, or a function that takes a
+ * resolution and returns an array of styles. To unset the feature style, call
+ * `setStyle()` without arguments or a falsey value.
+ * @param {import("./style/Style.js").StyleLike=} opt_style Style for this feature.
+ * @api
+ * @fires module:ol/events/Event~BaseEvent#event:change
+ */
+ setStyle(opt_style) {
+ this.style_ = opt_style;
+ this.styleFunction_ = !opt_style
+ ? undefined
+ : createStyleFunction(opt_style);
+ this.changed();
+ }
+
+ /**
+ * Set the feature id. The feature id is considered stable and may be used when
+ * requesting features or comparing identifiers returned from a remote source.
+ * The feature id can be used with the
+ * {@link module:ol/source/Vector~VectorSource#getFeatureById} method.
+ * @param {number|string|undefined} id The feature id.
+ * @api
+ * @fires module:ol/events/Event~BaseEvent#event:change
+ */
+ setId(id) {
+ this.id_ = id;
+ this.changed();
+ }
+
+ /**
+ * Set the property name to be used when getting the feature's default geometry.
+ * When calling {@link module:ol/Feature~Feature#getGeometry}, the value of the property with
+ * this name will be returned.
+ * @param {string} name The property name of the default geometry.
+ * @api
+ */
+ setGeometryName(name) {
+ this.removeEventListener(
+ getChangeEventType(this.geometryName_),
+ this.handleGeometryChanged_
+ );
+ this.geometryName_ = name;
+ this.addEventListener(
+ getChangeEventType(this.geometryName_),
+ this.handleGeometryChanged_
+ );
+ this.handleGeometryChanged_();
+ }
+}
+
+/**
+ * Convert the provided object into a feature style function. Functions passed
+ * through unchanged. Arrays of Style or single style objects wrapped
+ * in a new feature style function.
+ * @param {!import("./style/Style.js").StyleFunction|!Array|!import("./style/Style.js").default} obj
+ * A feature style function, a single style, or an array of styles.
+ * @return {import("./style/Style.js").StyleFunction} A style function.
+ */
+export function createStyleFunction(obj) {
+ if (typeof obj === 'function') {
+ return obj;
+ } else {
+ /**
+ * @type {Array}
+ */
+ let styles;
+ if (Array.isArray(obj)) {
+ styles = obj;
+ } else {
+ assert(typeof (/** @type {?} */ (obj).getZIndex) === 'function', 41); // Expected an `import("./style/Style.js").Style` or an array of `import("./style/Style.js").Style`
+ const style = /** @type {import("./style/Style.js").default} */ (obj);
+ styles = [style];
+ }
+ return function () {
+ return styles;
+ };
+ }
+}
+export default Feature;
\ No newline at end of file
diff --git a/src/common/format/GML.js b/src/common/format/GML.js
new file mode 100644
index 000000000..8b24bdd5b
--- /dev/null
+++ b/src/common/format/GML.js
@@ -0,0 +1,40 @@
+/**
+ * @module ol/format/GML
+ */
+import GML3 from './GML3.js';
+
+/**
+ * @classdesc
+ * Feature format for reading and writing data in the GML format
+ * version 3.1.1.
+ * Currently only supports GML 3.1.1 Simple Features profile.
+ *
+ * @param {import("./GMLBase.js").Options=} opt_options
+ * Optional configuration object.
+ * @api
+ */
+const GML = GML3;
+
+/**
+ * Encode an array of features in GML 3.1.1 Simple Features.
+ *
+ * @function
+ * @param {Array} features Features.
+ * @param {import("./Feature.js").WriteOptions=} opt_options Options.
+ * @return {string} Result.
+ * @api
+ */
+GML.prototype.writeFeatures;
+
+/**
+ * Encode an array of features in the GML 3.1.1 format as an XML node.
+ *
+ * @function
+ * @param {Array} features Features.
+ * @param {import("./Feature.js").WriteOptions=} opt_options Options.
+ * @return {Node} Node.
+ * @api
+ */
+GML.prototype.writeFeaturesNode;
+
+export default GML;
\ No newline at end of file
diff --git a/src/common/format/GML2.js b/src/common/format/GML2.js
new file mode 100644
index 000000000..8010eaec0
--- /dev/null
+++ b/src/common/format/GML2.js
@@ -0,0 +1,785 @@
+/**
+ * @module ol/format/GML2
+ */
+import GMLBase, {GMLNS} from './GMLBase.js';
+import {
+ OBJECT_PROPERTY_NODE_FACTORY,
+ createElementNS,
+ getAllTextContent,
+ makeArrayPusher,
+ makeChildAppender,
+ makeReplacer,
+ makeSimpleNodeFactory,
+ pushParseAndPop,
+ pushSerializeAndPop,
+} from './util/xml.js';
+import {assign} from './util/obj.js';
+import {createOrUpdate} from '../extent.js';
+import {get as getProjection} from '../proj.js';
+import {
+ transformExtentWithOptions,
+ transformGeometryWithOptions,
+} from './Feature.js';
+import {writeStringTextNode} from './xsd.js';
+
+/**
+ * @const
+ * @type {string}
+ */
+const schemaLocation =
+ GMLNS + ' http://schemas.opengis.net/gml/2.1.2/feature.xsd';
+
+/**
+ * @const
+ * @type {Object}
+ */
+const MULTIGEOMETRY_TO_MEMBER_NODENAME = {
+ 'MultiLineString': 'lineStringMember',
+ 'MultiCurve': 'curveMember',
+ 'MultiPolygon': 'polygonMember',
+ 'MultiSurface': 'surfaceMember',
+};
+
+/**
+ * @classdesc
+ * Feature format for reading and writing data in the GML format,
+ * version 2.1.2.
+ *
+ * @api
+ */
+class GML2 extends GMLBase {
+ /**
+ * @param {import("./GMLBase.js").Options=} opt_options Optional configuration object.
+ */
+ constructor(opt_options) {
+ const options =
+ /** @type {import("./GMLBase.js").Options} */
+ (opt_options ? opt_options : {});
+
+ super(options);
+
+ this.FEATURE_COLLECTION_PARSERS[GMLNS]['featureMember'] = makeArrayPusher(
+ this.readFeaturesInternal
+ );
+
+ /**
+ * @type {string}
+ */
+ this.schemaLocation = options.schemaLocation
+ ? options.schemaLocation
+ : schemaLocation;
+ }
+
+ /**
+ * @param {Node} node Node.
+ * @param {Array<*>} objectStack Object stack.
+ * @return {Array|undefined} Flat coordinates.
+ */
+ readFlatCoordinates(node, objectStack) {
+ const s = getAllTextContent(node, false).replace(/^\s*|\s*$/g, '');
+ const context = /** @type {import("../xml.js").NodeStackItem} */ (objectStack[0]);
+ const containerSrs = context['srsName'];
+ let axisOrientation = 'enu';
+ if (containerSrs) {
+ const proj = getProjection(containerSrs);
+ if (proj) {
+ axisOrientation = proj.getAxisOrientation();
+ }
+ }
+ const coordsGroups = s.trim().split(/\s+/);
+ const flatCoordinates = [];
+ for (let i = 0, ii = coordsGroups.length; i < ii; i++) {
+ const coords = coordsGroups[i].split(/,+/);
+ const x = parseFloat(coords[0]);
+ const y = parseFloat(coords[1]);
+ const z = coords.length === 3 ? parseFloat(coords[2]) : 0;
+ if (axisOrientation.substr(0, 2) === 'en') {
+ flatCoordinates.push(x, y, z);
+ } else {
+ flatCoordinates.push(y, x, z);
+ }
+ }
+ return flatCoordinates;
+ }
+
+ /**
+ * @param {Element} node Node.
+ * @param {Array<*>} objectStack Object stack.
+ * @return {import("../extent.js").Extent|undefined} Envelope.
+ */
+ readBox(node, objectStack) {
+ /** @type {Array} */
+ const flatCoordinates = pushParseAndPop(
+ [null],
+ this.BOX_PARSERS_,
+ node,
+ objectStack,
+ this
+ );
+ return createOrUpdate(
+ flatCoordinates[1][0],
+ flatCoordinates[1][1],
+ flatCoordinates[1][3],
+ flatCoordinates[1][4]
+ );
+ }
+
+ /**
+ * @param {Element} node Node.
+ * @param {Array<*>} objectStack Object stack.
+ */
+ innerBoundaryIsParser(node, objectStack) {
+ /** @type {Array|undefined} */
+ const flatLinearRing = pushParseAndPop(
+ undefined,
+ this.RING_PARSERS,
+ node,
+ objectStack,
+ this
+ );
+ if (flatLinearRing) {
+ const flatLinearRings =
+ /** @type {Array>} */
+ (objectStack[objectStack.length - 1]);
+ flatLinearRings.push(flatLinearRing);
+ }
+ }
+
+ /**
+ * @param {Element} node Node.
+ * @param {Array<*>} objectStack Object stack.
+ */
+ outerBoundaryIsParser(node, objectStack) {
+ /** @type {Array|undefined} */
+ const flatLinearRing = pushParseAndPop(
+ undefined,
+ this.RING_PARSERS,
+ node,
+ objectStack,
+ this
+ );
+ if (flatLinearRing) {
+ const flatLinearRings =
+ /** @type {Array>} */
+ (objectStack[objectStack.length - 1]);
+ flatLinearRings[0] = flatLinearRing;
+ }
+ }
+
+ /**
+ * @const
+ * @param {*} value Value.
+ * @param {Array<*>} objectStack Object stack.
+ * @param {string=} opt_nodeName Node name.
+ * @return {Element|undefined} Node.
+ * @private
+ */
+ GEOMETRY_NODE_FACTORY_(value, objectStack, opt_nodeName) {
+ const context = objectStack[objectStack.length - 1];
+ const multiSurface = context['multiSurface'];
+ const surface = context['surface'];
+ const multiCurve = context['multiCurve'];
+ let nodeName;
+ if (!Array.isArray(value)) {
+ nodeName = /** @type {import("../geom/Geometry.js").default} */ (value).getType();
+ if (nodeName === 'MultiPolygon' && multiSurface === true) {
+ nodeName = 'MultiSurface';
+ } else if (nodeName === 'Polygon' && surface === true) {
+ nodeName = 'Surface';
+ } else if (nodeName === 'MultiLineString' && multiCurve === true) {
+ nodeName = 'MultiCurve';
+ }
+ } else {
+ nodeName = 'Envelope';
+ }
+ return createElementNS('http://www.opengis.net/gml', nodeName);
+ }
+
+ /**
+ * @param {Element} node Node.
+ * @param {import("../Feature.js").default} feature Feature.
+ * @param {Array<*>} objectStack Node stack.
+ */
+ writeFeatureElement(node, feature, objectStack) {
+ const fid = feature.getId();
+ if (fid) {
+ node.setAttribute('fid', /** @type {string} */ (fid));
+ }
+ const context = /** @type {Object} */ (objectStack[objectStack.length - 1]);
+ const featureNS = context['featureNS'];
+ const geometryName = feature.getGeometryName();
+ if (!context.serializers) {
+ context.serializers = {};
+ context.serializers[featureNS] = {};
+ }
+ const keys = [];
+ const values = [];
+ if (feature.hasProperties()) {
+ const properties = feature.getProperties();
+ for (const key in properties) {
+ const value = properties[key];
+ if (value !== null) {
+ keys.push(key);
+ values.push(value);
+ if (
+ key == geometryName ||
+ typeof (/** @type {?} */ (value).getSimplifiedGeometry) ===
+ 'function'
+ ) {
+ if (!(key in context.serializers[featureNS])) {
+ context.serializers[featureNS][key] = makeChildAppender(
+ this.writeGeometryElement,
+ this
+ );
+ }
+ } else {
+ if (!(key in context.serializers[featureNS])) {
+ context.serializers[featureNS][key] = makeChildAppender(
+ writeStringTextNode
+ );
+ }
+ }
+ }
+ }
+ }
+ const item = assign({}, context);
+ item.node = node;
+ pushSerializeAndPop(
+ /** @type {import("../xml.js").NodeStackItem} */
+ (item),
+ context.serializers,
+ makeSimpleNodeFactory(undefined, featureNS),
+ values,
+ objectStack,
+ keys
+ );
+ }
+
+ /**
+ * @param {Element} node Node.
+ * @param {import("../geom/LineString.js").default} geometry LineString geometry.
+ * @param {Array<*>} objectStack Node stack.
+ */
+ writeCurveOrLineString(node, geometry, objectStack) {
+ const context = objectStack[objectStack.length - 1];
+ const srsName = context['srsName'];
+ if (node.nodeName !== 'LineStringSegment' && srsName) {
+ node.setAttribute('srsName', srsName);
+ }
+ if (
+ node.nodeName === 'LineString' ||
+ node.nodeName === 'LineStringSegment'
+ ) {
+ const coordinates = this.createCoordinatesNode_(node.namespaceURI);
+ node.appendChild(coordinates);
+ this.writeCoordinates_(coordinates, geometry, objectStack);
+ } else if (node.nodeName === 'Curve') {
+ const segments = createElementNS(node.namespaceURI, 'segments');
+ node.appendChild(segments);
+ this.writeCurveSegments_(segments, geometry, objectStack);
+ }
+ }
+
+ /**
+ * @param {Element} node Node.
+ * @param {import("../geom/LineString.js").default} line LineString geometry.
+ * @param {Array<*>} objectStack Node stack.
+ */
+ writeLineStringOrCurveMember(node, line, objectStack) {
+ const child = this.GEOMETRY_NODE_FACTORY_(line, objectStack);
+ if (child) {
+ node.appendChild(child);
+ this.writeCurveOrLineString(child, line, objectStack);
+ }
+ }
+
+ /**
+ * @param {Element} node Node.
+ * @param {import("../geom/MultiLineString.js").default} geometry MultiLineString geometry.
+ * @param {Array<*>} objectStack Node stack.
+ */
+ writeMultiCurveOrLineString(node, geometry, objectStack) {
+ const context = objectStack[objectStack.length - 1];
+ const hasZ = context['hasZ'];
+ const srsName = context['srsName'];
+ const curve = context['curve'];
+ if (srsName) {
+ node.setAttribute('srsName', srsName);
+ }
+ const lines = geometry.getLineStrings();
+ pushSerializeAndPop(
+ {node: node, hasZ: hasZ, srsName: srsName, curve: curve},
+ this.LINESTRINGORCURVEMEMBER_SERIALIZERS,
+ this.MULTIGEOMETRY_MEMBER_NODE_FACTORY_,
+ lines,
+ objectStack,
+ undefined,
+ this
+ );
+ }
+
+ /**
+ * @param {Node} node Node.
+ * @param {import("../geom/Geometry.js").default|import("../extent.js").Extent} geometry Geometry.
+ * @param {Array<*>} objectStack Node stack.
+ */
+ writeGeometryElement(node, geometry, objectStack) {
+ const context = /** @type {import("./Feature.js").WriteOptions} */ (objectStack[
+ objectStack.length - 1
+ ]);
+ const item = assign({}, context);
+ item['node'] = node;
+ let value;
+ if (Array.isArray(geometry)) {
+ value = transformExtentWithOptions(
+ /** @type {import("../extent.js").Extent} */ (geometry),
+ context
+ );
+ } else {
+ value = transformGeometryWithOptions(
+ /** @type {import("../geom/Geometry.js").default} */ (geometry),
+ true,
+ context
+ );
+ }
+ pushSerializeAndPop(
+ /** @type {import("../xml.js").NodeStackItem} */
+ (item),
+ this.GEOMETRY_SERIALIZERS,
+ this.GEOMETRY_NODE_FACTORY_,
+ [value],
+ objectStack,
+ undefined,
+ this
+ );
+ }
+
+ /**
+ * @param {string} namespaceURI XML namespace.
+ * @return {Element} coordinates node.
+ * @private
+ */
+ createCoordinatesNode_(namespaceURI) {
+ const coordinates = createElementNS(namespaceURI, 'coordinates');
+ coordinates.setAttribute('decimal', '.');
+ coordinates.setAttribute('cs', ',');
+ coordinates.setAttribute('ts', ' ');
+
+ return coordinates;
+ }
+
+ /**
+ * @param {Node} node Node.
+ * @param {import("../geom/LineString.js").default|import("../geom/LinearRing.js").default} value Geometry.
+ * @param {Array<*>} objectStack Node stack.
+ * @private
+ */
+ writeCoordinates_(node, value, objectStack) {
+ const context = objectStack[objectStack.length - 1];
+ const hasZ = context['hasZ'];
+ const srsName = context['srsName'];
+ // only 2d for simple features profile
+ const points = value.getCoordinates();
+ const len = points.length;
+ const parts = new Array(len);
+ for (let i = 0; i < len; ++i) {
+ const point = points[i];
+ parts[i] = this.getCoords_(point, srsName, hasZ);
+ }
+ writeStringTextNode(node, parts.join(' '));
+ }
+
+ /**
+ * @param {Node} node Node.
+ * @param {import("../geom/LineString.js").default} line LineString geometry.
+ * @param {Array<*>} objectStack Node stack.
+ * @private
+ */
+ writeCurveSegments_(node, line, objectStack) {
+ const child = createElementNS(node.namespaceURI, 'LineStringSegment');
+ node.appendChild(child);
+ this.writeCurveOrLineString(child, line, objectStack);
+ }
+
+ /**
+ * @param {Element} node Node.
+ * @param {import("../geom/Polygon.js").default} geometry Polygon geometry.
+ * @param {Array<*>} objectStack Node stack.
+ */
+ writeSurfaceOrPolygon(node, geometry, objectStack) {
+ const context = objectStack[objectStack.length - 1];
+ const hasZ = context['hasZ'];
+ const srsName = context['srsName'];
+ if (node.nodeName !== 'PolygonPatch' && srsName) {
+ node.setAttribute('srsName', srsName);
+ }
+ if (node.nodeName === 'Polygon' || node.nodeName === 'PolygonPatch') {
+ const rings = geometry.getLinearRings();
+ pushSerializeAndPop(
+ {node: node, hasZ: hasZ, srsName: srsName},
+ this.RING_SERIALIZERS,
+ this.RING_NODE_FACTORY_,
+ rings,
+ objectStack,
+ undefined,
+ this
+ );
+ } else if (node.nodeName === 'Surface') {
+ const patches = createElementNS(node.namespaceURI, 'patches');
+ node.appendChild(patches);
+ this.writeSurfacePatches_(patches, geometry, objectStack);
+ }
+ }
+
+ /**
+ * @param {*} value Value.
+ * @param {Array<*>} objectStack Object stack.
+ * @param {string=} opt_nodeName Node name.
+ * @return {Node} Node.
+ * @private
+ */
+ RING_NODE_FACTORY_(value, objectStack, opt_nodeName) {
+ const context = objectStack[objectStack.length - 1];
+ const parentNode = context.node;
+ const exteriorWritten = context['exteriorWritten'];
+ if (exteriorWritten === undefined) {
+ context['exteriorWritten'] = true;
+ }
+ return createElementNS(
+ parentNode.namespaceURI,
+ exteriorWritten !== undefined ? 'innerBoundaryIs' : 'outerBoundaryIs'
+ );
+ }
+
+ /**
+ * @param {Node} node Node.
+ * @param {import("../geom/Polygon.js").default} polygon Polygon geometry.
+ * @param {Array<*>} objectStack Node stack.
+ * @private
+ */
+ writeSurfacePatches_(node, polygon, objectStack) {
+ const child = createElementNS(node.namespaceURI, 'PolygonPatch');
+ node.appendChild(child);
+ this.writeSurfaceOrPolygon(child, polygon, objectStack);
+ }
+
+ /**
+ * @param {Node} node Node.
+ * @param {import("../geom/LinearRing.js").default} ring LinearRing geometry.
+ * @param {Array<*>} objectStack Node stack.
+ */
+ writeRing(node, ring, objectStack) {
+ const linearRing = createElementNS(node.namespaceURI, 'LinearRing');
+ node.appendChild(linearRing);
+ this.writeLinearRing(linearRing, ring, objectStack);
+ }
+
+ /**
+ * @param {Array} point Point geometry.
+ * @param {string=} opt_srsName Optional srsName
+ * @param {boolean=} opt_hasZ whether the geometry has a Z coordinate (is 3D) or not.
+ * @return {string} The coords string.
+ * @private
+ */
+ getCoords_(point, opt_srsName, opt_hasZ) {
+ let axisOrientation = 'enu';
+ if (opt_srsName) {
+ axisOrientation = getProjection(opt_srsName).getAxisOrientation();
+ }
+ let coords =
+ axisOrientation.substr(0, 2) === 'en'
+ ? point[0] + ',' + point[1]
+ : point[1] + ',' + point[0];
+ if (opt_hasZ) {
+ // For newly created points, Z can be undefined.
+ const z = point[2] || 0;
+ coords += ',' + z;
+ }
+
+ return coords;
+ }
+
+ /**
+ * @param {Element} node Node.
+ * @param {import("../geom/Point.js").default} geometry Point geometry.
+ * @param {Array<*>} objectStack Node stack.
+ */
+ writePoint(node, geometry, objectStack) {
+ const context = objectStack[objectStack.length - 1];
+ const hasZ = context['hasZ'];
+ const srsName = context['srsName'];
+ if (srsName) {
+ node.setAttribute('srsName', srsName);
+ }
+ const coordinates = this.createCoordinatesNode_(node.namespaceURI);
+ node.appendChild(coordinates);
+ const point = geometry.getCoordinates();
+ const coord = this.getCoords_(point, srsName, hasZ);
+ writeStringTextNode(coordinates, coord);
+ }
+
+ /**
+ * @param {Element} node Node.
+ * @param {import("../geom/MultiPoint.js").default} geometry MultiPoint geometry.
+ * @param {Array<*>} objectStack Node stack.
+ */
+ writeMultiPoint(node, geometry, objectStack) {
+ const context = objectStack[objectStack.length - 1];
+ const hasZ = context['hasZ'];
+ const srsName = context['srsName'];
+ if (srsName) {
+ node.setAttribute('srsName', srsName);
+ }
+ const points = geometry.getPoints();
+ pushSerializeAndPop(
+ {node: node, hasZ: hasZ, srsName: srsName},
+ this.POINTMEMBER_SERIALIZERS,
+ makeSimpleNodeFactory('pointMember'),
+ points,
+ objectStack,
+ undefined,
+ this
+ );
+ }
+
+ /**
+ * @param {Node} node Node.
+ * @param {import("../geom/Point.js").default} point Point geometry.
+ * @param {Array<*>} objectStack Node stack.
+ */
+ writePointMember(node, point, objectStack) {
+ const child = createElementNS(node.namespaceURI, 'Point');
+ node.appendChild(child);
+ this.writePoint(child, point, objectStack);
+ }
+
+ /**
+ * @param {Element} node Node.
+ * @param {import("../geom/LinearRing.js").default} geometry LinearRing geometry.
+ * @param {Array<*>} objectStack Node stack.
+ */
+ writeLinearRing(node, geometry, objectStack) {
+ const context = objectStack[objectStack.length - 1];
+ const srsName = context['srsName'];
+ if (srsName) {
+ node.setAttribute('srsName', srsName);
+ }
+ const coordinates = this.createCoordinatesNode_(node.namespaceURI);
+ node.appendChild(coordinates);
+ this.writeCoordinates_(coordinates, geometry, objectStack);
+ }
+
+ /**
+ * @param {Element} node Node.
+ * @param {import("../geom/MultiPolygon.js").default} geometry MultiPolygon geometry.
+ * @param {Array<*>} objectStack Node stack.
+ */
+ writeMultiSurfaceOrPolygon(node, geometry, objectStack) {
+ const context = objectStack[objectStack.length - 1];
+ const hasZ = context['hasZ'];
+ const srsName = context['srsName'];
+ const surface = context['surface'];
+ if (srsName) {
+ node.setAttribute('srsName', srsName);
+ }
+ const polygons = geometry.getPolygons();
+ pushSerializeAndPop(
+ {node: node, hasZ: hasZ, srsName: srsName, surface: surface},
+ this.SURFACEORPOLYGONMEMBER_SERIALIZERS,
+ this.MULTIGEOMETRY_MEMBER_NODE_FACTORY_,
+ polygons,
+ objectStack,
+ undefined,
+ this
+ );
+ }
+
+ /**
+ * @param {Node} node Node.
+ * @param {import("../geom/Polygon.js").default} polygon Polygon geometry.
+ * @param {Array<*>} objectStack Node stack.
+ */
+ writeSurfaceOrPolygonMember(node, polygon, objectStack) {
+ const child = this.GEOMETRY_NODE_FACTORY_(polygon, objectStack);
+ if (child) {
+ node.appendChild(child);
+ this.writeSurfaceOrPolygon(child, polygon, objectStack);
+ }
+ }
+
+ /**
+ * @param {Element} node Node.
+ * @param {import("../extent.js").Extent} extent Extent.
+ * @param {Array<*>} objectStack Node stack.
+ */
+ writeEnvelope(node, extent, objectStack) {
+ const context = objectStack[objectStack.length - 1];
+ const srsName = context['srsName'];
+ if (srsName) {
+ node.setAttribute('srsName', srsName);
+ }
+ const keys = ['lowerCorner', 'upperCorner'];
+ const values = [extent[0] + ' ' + extent[1], extent[2] + ' ' + extent[3]];
+ pushSerializeAndPop(
+ /** @type {import("../xml.js").NodeStackItem} */
+ ({node: node}),
+ this.ENVELOPE_SERIALIZERS,
+ OBJECT_PROPERTY_NODE_FACTORY,
+ values,
+ objectStack,
+ keys,
+ this
+ );
+ }
+
+ /**
+ * @const
+ * @param {*} value Value.
+ * @param {Array<*>} objectStack Object stack.
+ * @param {string=} opt_nodeName Node name.
+ * @return {Node|undefined} Node.
+ * @private
+ */
+ MULTIGEOMETRY_MEMBER_NODE_FACTORY_(value, objectStack, opt_nodeName) {
+ const parentNode = objectStack[objectStack.length - 1].node;
+ return createElementNS(
+ 'http://www.opengis.net/gml',
+ MULTIGEOMETRY_TO_MEMBER_NODENAME[parentNode.nodeName]
+ );
+ }
+}
+
+/**
+ * @const
+ * @type {Object>}
+ */
+GML2.prototype.GEOMETRY_FLAT_COORDINATES_PARSERS = {
+ 'http://www.opengis.net/gml': {
+ 'coordinates': makeReplacer(GML2.prototype.readFlatCoordinates),
+ },
+};
+
+/**
+ * @const
+ * @type {Object>}
+ */
+GML2.prototype.FLAT_LINEAR_RINGS_PARSERS = {
+ 'http://www.opengis.net/gml': {
+ 'innerBoundaryIs': GML2.prototype.innerBoundaryIsParser,
+ 'outerBoundaryIs': GML2.prototype.outerBoundaryIsParser,
+ },
+};
+
+/**
+ * @const
+ * @type {Object>}
+ */
+GML2.prototype.BOX_PARSERS_ = {
+ 'http://www.opengis.net/gml': {
+ 'coordinates': makeArrayPusher(GML2.prototype.readFlatCoordinates),
+ },
+};
+
+/**
+ * @const
+ * @type {Object>}
+ */
+GML2.prototype.GEOMETRY_PARSERS = {
+ 'http://www.opengis.net/gml': {
+ 'Point': makeReplacer(GMLBase.prototype.readPoint),
+ 'MultiPoint': makeReplacer(GMLBase.prototype.readMultiPoint),
+ 'LineString': makeReplacer(GMLBase.prototype.readLineString),
+ 'MultiLineString': makeReplacer(GMLBase.prototype.readMultiLineString),
+ 'LinearRing': makeReplacer(GMLBase.prototype.readLinearRing),
+ 'Polygon': makeReplacer(GMLBase.prototype.readPolygon),
+ 'MultiPolygon': makeReplacer(GMLBase.prototype.readMultiPolygon),
+ 'Box': makeReplacer(GML2.prototype.readBox),
+ },
+};
+
+/**
+ * @const
+ * @type {Object>}
+ */
+GML2.prototype.GEOMETRY_SERIALIZERS = {
+ 'http://www.opengis.net/gml': {
+ 'Curve': makeChildAppender(GML2.prototype.writeCurveOrLineString),
+ 'MultiCurve': makeChildAppender(GML2.prototype.writeMultiCurveOrLineString),
+ 'Point': makeChildAppender(GML2.prototype.writePoint),
+ 'MultiPoint': makeChildAppender(GML2.prototype.writeMultiPoint),
+ 'LineString': makeChildAppender(GML2.prototype.writeCurveOrLineString),
+ 'MultiLineString': makeChildAppender(
+ GML2.prototype.writeMultiCurveOrLineString
+ ),
+ 'LinearRing': makeChildAppender(GML2.prototype.writeLinearRing),
+ 'Polygon': makeChildAppender(GML2.prototype.writeSurfaceOrPolygon),
+ 'MultiPolygon': makeChildAppender(
+ GML2.prototype.writeMultiSurfaceOrPolygon
+ ),
+ 'Surface': makeChildAppender(GML2.prototype.writeSurfaceOrPolygon),
+ 'MultiSurface': makeChildAppender(
+ GML2.prototype.writeMultiSurfaceOrPolygon
+ ),
+ 'Envelope': makeChildAppender(GML2.prototype.writeEnvelope),
+ },
+};
+
+/**
+ * @type {Object>}
+ */
+GML2.prototype.LINESTRINGORCURVEMEMBER_SERIALIZERS = {
+ 'http://www.opengis.net/gml': {
+ 'lineStringMember': makeChildAppender(
+ GML2.prototype.writeLineStringOrCurveMember
+ ),
+ 'curveMember': makeChildAppender(
+ GML2.prototype.writeLineStringOrCurveMember
+ ),
+ },
+};
+
+/**
+ * @type {Object>}
+ */
+GML2.prototype.RING_SERIALIZERS = {
+ 'http://www.opengis.net/gml': {
+ 'outerBoundaryIs': makeChildAppender(GML2.prototype.writeRing),
+ 'innerBoundaryIs': makeChildAppender(GML2.prototype.writeRing),
+ },
+};
+
+/**
+ * @type {Object>}
+ */
+GML2.prototype.POINTMEMBER_SERIALIZERS = {
+ 'http://www.opengis.net/gml': {
+ 'pointMember': makeChildAppender(GML2.prototype.writePointMember),
+ },
+};
+
+/**
+ * @const
+ * @type {Object>}
+ */
+GML2.prototype.SURFACEORPOLYGONMEMBER_SERIALIZERS = {
+ 'http://www.opengis.net/gml': {
+ 'surfaceMember': makeChildAppender(
+ GML2.prototype.writeSurfaceOrPolygonMember
+ ),
+ 'polygonMember': makeChildAppender(
+ GML2.prototype.writeSurfaceOrPolygonMember
+ ),
+ },
+};
+
+/**
+ * @type {Object>}
+ */
+GML2.prototype.ENVELOPE_SERIALIZERS = {
+ 'http://www.opengis.net/gml': {
+ 'lowerCorner': makeChildAppender(writeStringTextNode),
+ 'upperCorner': makeChildAppender(writeStringTextNode),
+ },
+};
+
+export default GML2;
\ No newline at end of file
diff --git a/src/common/format/GML3.js b/src/common/format/GML3.js
new file mode 100644
index 000000000..d34ab2790
--- /dev/null
+++ b/src/common/format/GML3.js
@@ -0,0 +1,1261 @@
+/**
+ * @module ol/format/GML3
+ */
+import GML2 from './GML2.js';
+import GMLBase, {GMLNS} from './GMLBase.js';
+import GeometryLayout from '../geom/GeometryLayout.js';
+import LineString from '../geom/LineString.js';
+import MultiLineString from '../geom/MultiLineString.js';
+import MultiPolygon from '../geom/MultiPolygon.js';
+import Polygon from '../geom/Polygon.js';
+import {
+ OBJECT_PROPERTY_NODE_FACTORY,
+ XML_SCHEMA_INSTANCE_URI,
+ createElementNS,
+ getAllTextContent,
+ makeArrayPusher,
+ makeChildAppender,
+ makeReplacer,
+ makeSimpleNodeFactory,
+ parseNode,
+ pushParseAndPop,
+ pushSerializeAndPop,
+} from '../xml.js';
+import {assign} from '../obj.js';
+import {createOrUpdate} from '../extent.js';
+import {extend} from '../array.js';
+import {get as getProjection} from '../proj.js';
+import {readNonNegativeIntegerString, writeStringTextNode} from './xsd.js';
+import {
+ transformExtentWithOptions,
+ transformGeometryWithOptions,
+} from './Feature.js';
+
+/**
+ * @const
+ * @type {string}
+ * @private
+ */
+const schemaLocation =
+ GMLNS +
+ ' http://schemas.opengis.net/gml/3.1.1/profiles/gmlsfProfile/' +
+ '1.0.0/gmlsf.xsd';
+
+/**
+ * @const
+ * @type {Object}
+ */
+const MULTIGEOMETRY_TO_MEMBER_NODENAME = {
+ 'MultiLineString': 'lineStringMember',
+ 'MultiCurve': 'curveMember',
+ 'MultiPolygon': 'polygonMember',
+ 'MultiSurface': 'surfaceMember',
+};
+
+/**
+ * @classdesc
+ * Feature format for reading and writing data in the GML format
+ * version 3.1.1.
+ * Currently only supports GML 3.1.1 Simple Features profile.
+ *
+ * @api
+ */
+class GML3 extends GMLBase {
+ /**
+ * @param {import("./GMLBase.js").Options=} opt_options Optional configuration object.
+ */
+ constructor(opt_options) {
+ const options =
+ /** @type {import("./GMLBase.js").Options} */
+ (opt_options ? opt_options : {});
+
+ super(options);
+
+ /**
+ * @private
+ * @type {boolean}
+ */
+ this.surface_ = options.surface !== undefined ? options.surface : false;
+
+ /**
+ * @private
+ * @type {boolean}
+ */
+ this.curve_ = options.curve !== undefined ? options.curve : false;
+
+ /**
+ * @private
+ * @type {boolean}
+ */
+ this.multiCurve_ =
+ options.multiCurve !== undefined ? options.multiCurve : true;
+
+ /**
+ * @private
+ * @type {boolean}
+ */
+ this.multiSurface_ =
+ options.multiSurface !== undefined ? options.multiSurface : true;
+
+ /**
+ * @type {string}
+ */
+ this.schemaLocation = options.schemaLocation
+ ? options.schemaLocation
+ : schemaLocation;
+
+ /**
+ * @private
+ * @type {boolean}
+ */
+ this.hasZ = options.hasZ !== undefined ? options.hasZ : false;
+ }
+
+ /**
+ * @param {Element} node Node.
+ * @param {Array<*>} objectStack Object stack.
+ * @return {MultiLineString|undefined} MultiLineString.
+ */
+ readMultiCurve(node, objectStack) {
+ /** @type {Array} */
+ const lineStrings = pushParseAndPop(
+ [],
+ this.MULTICURVE_PARSERS,
+ node,
+ objectStack,
+ this
+ );
+ if (lineStrings) {
+ const multiLineString = new MultiLineString(lineStrings);
+ return multiLineString;
+ } else {
+ return undefined;
+ }
+ }
+
+ /**
+ * @param {Element} node Node.
+ * @param {Array<*>} objectStack Object stack.
+ * @return {MultiPolygon|undefined} MultiPolygon.
+ */
+ readMultiSurface(node, objectStack) {
+ /** @type {Array} */
+ const polygons = pushParseAndPop(
+ [],
+ this.MULTISURFACE_PARSERS,
+ node,
+ objectStack,
+ this
+ );
+ if (polygons) {
+ return new MultiPolygon(polygons);
+ }
+ }
+
+ /**
+ * @param {Element} node Node.
+ * @param {Array<*>} objectStack Object stack.
+ */
+ curveMemberParser(node, objectStack) {
+ parseNode(this.CURVEMEMBER_PARSERS, node, objectStack, this);
+ }
+
+ /**
+ * @param {Element} node Node.
+ * @param {Array<*>} objectStack Object stack.
+ */
+ surfaceMemberParser(node, objectStack) {
+ parseNode(this.SURFACEMEMBER_PARSERS, node, objectStack, this);
+ }
+
+ /**
+ * @param {Element} node Node.
+ * @param {Array<*>} objectStack Object stack.
+ * @return {Array<(Array)>|undefined} flat coordinates.
+ */
+ readPatch(node, objectStack) {
+ return pushParseAndPop(
+ [null],
+ this.PATCHES_PARSERS,
+ node,
+ objectStack,
+ this
+ );
+ }
+
+ /**
+ * @param {Element} node Node.
+ * @param {Array<*>} objectStack Object stack.
+ * @return {Array|undefined} flat coordinates.
+ */
+ readSegment(node, objectStack) {
+ return pushParseAndPop(
+ [null],
+ this.SEGMENTS_PARSERS,
+ node,
+ objectStack,
+ this
+ );
+ }
+
+ /**
+ * @param {Element} node Node.
+ * @param {Array<*>} objectStack Object stack.
+ * @return {Array<(Array)>|undefined} flat coordinates.
+ */
+ readPolygonPatch(node, objectStack) {
+ return pushParseAndPop(
+ [null],
+ this.FLAT_LINEAR_RINGS_PARSERS,
+ node,
+ objectStack,
+ this
+ );
+ }
+
+ /**
+ * @param {Element} node Node.
+ * @param {Array<*>} objectStack Object stack.
+ * @return {Array|undefined} flat coordinates.
+ */
+ readLineStringSegment(node, objectStack) {
+ return pushParseAndPop(
+ [null],
+ this.GEOMETRY_FLAT_COORDINATES_PARSERS,
+ node,
+ objectStack,
+ this
+ );
+ }
+
+ /**
+ * @param {Element} node Node.
+ * @param {Array<*>} objectStack Object stack.
+ */
+ interiorParser(node, objectStack) {
+ /** @type {Array|undefined} */
+ const flatLinearRing = pushParseAndPop(
+ undefined,
+ this.RING_PARSERS,
+ node,
+ objectStack,
+ this
+ );
+ if (flatLinearRing) {
+ const flatLinearRings =
+ /** @type {Array>} */
+ (objectStack[objectStack.length - 1]);
+ flatLinearRings.push(flatLinearRing);
+ }
+ }
+
+ /**
+ * @param {Element} node Node.
+ * @param {Array<*>} objectStack Object stack.
+ */
+ exteriorParser(node, objectStack) {
+ /** @type {Array|undefined} */
+ const flatLinearRing = pushParseAndPop(
+ undefined,
+ this.RING_PARSERS,
+ node,
+ objectStack,
+ this
+ );
+ if (flatLinearRing) {
+ const flatLinearRings =
+ /** @type {Array>} */
+ (objectStack[objectStack.length - 1]);
+ flatLinearRings[0] = flatLinearRing;
+ }
+ }
+
+ /**
+ * @param {Element} node Node.
+ * @param {Array<*>} objectStack Object stack.
+ * @return {Polygon|undefined} Polygon.
+ */
+ readSurface(node, objectStack) {
+ /** @type {Array>} */
+ const flatLinearRings = pushParseAndPop(
+ [null],
+ this.SURFACE_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, GeometryLayout.XYZ, ends);
+ } else {
+ return undefined;
+ }
+ }
+
+ /**
+ * @param {Element} node Node.
+ * @param {Array<*>} objectStack Object stack.
+ * @return {LineString|undefined} LineString.
+ */
+ readCurve(node, objectStack) {
+ /** @type {Array} */
+ const flatCoordinates = pushParseAndPop(
+ [null],
+ this.CURVE_PARSERS,
+ node,
+ objectStack,
+ this
+ );
+ if (flatCoordinates) {
+ const lineString = new LineString(flatCoordinates, GeometryLayout.XYZ);
+ return lineString;
+ } else {
+ return undefined;
+ }
+ }
+
+ /**
+ * @param {Element} node Node.
+ * @param {Array<*>} objectStack Object stack.
+ * @return {import("../extent.js").Extent|undefined} Envelope.
+ */
+ readEnvelope(node, objectStack) {
+ /** @type {Array} */
+ const flatCoordinates = pushParseAndPop(
+ [null],
+ this.ENVELOPE_PARSERS,
+ node,
+ objectStack,
+ this
+ );
+ return createOrUpdate(
+ flatCoordinates[1][0],
+ flatCoordinates[1][1],
+ flatCoordinates[2][0],
+ flatCoordinates[2][1]
+ );
+ }
+
+ /**
+ * @param {Node} node Node.
+ * @param {Array<*>} objectStack Object stack.
+ * @return {Array|undefined} Flat coordinates.
+ */
+ readFlatPos(node, objectStack) {
+ let s = getAllTextContent(node, false);
+ const re = /^\s*([+\-]?\d*\.?\d+(?:[eE][+\-]?\d+)?)\s*/;
+ /** @type {Array} */
+ const flatCoordinates = [];
+ let m;
+ while ((m = re.exec(s))) {
+ flatCoordinates.push(parseFloat(m[1]));
+ s = s.substr(m[0].length);
+ }
+ if (s !== '') {
+ return undefined;
+ }
+ const context = objectStack[0];
+ const containerSrs = context['srsName'];
+ let axisOrientation = 'enu';
+ if (containerSrs) {
+ const proj = getProjection(containerSrs);
+ axisOrientation = proj.getAxisOrientation();
+ }
+ if (axisOrientation === 'neu') {
+ let i, ii;
+ for (i = 0, ii = flatCoordinates.length; i < ii; i += 3) {
+ const y = flatCoordinates[i];
+ const x = flatCoordinates[i + 1];
+ flatCoordinates[i] = x;
+ flatCoordinates[i + 1] = y;
+ }
+ }
+ const len = flatCoordinates.length;
+ if (len == 2) {
+ flatCoordinates.push(0);
+ }
+ if (len === 0) {
+ return undefined;
+ }
+ return flatCoordinates;
+ }
+
+ /**
+ * @param {Element} node Node.
+ * @param {Array<*>} objectStack Object stack.
+ * @return {Array|undefined} Flat coordinates.
+ */
+ readFlatPosList(node, objectStack) {
+ const s = getAllTextContent(node, false).replace(/^\s*|\s*$/g, '');
+ const context = objectStack[0];
+ const containerSrs = context['srsName'];
+ const contextDimension = context['srsDimension'];
+ let axisOrientation = 'enu';
+ if (containerSrs) {
+ const proj = getProjection(containerSrs);
+ axisOrientation = proj.getAxisOrientation();
+ }
+ const coords = s.split(/\s+/);
+ // The "dimension" attribute is from the GML 3.0.1 spec.
+ let dim = 2;
+ if (node.getAttribute('srsDimension')) {
+ dim = readNonNegativeIntegerString(node.getAttribute('srsDimension'));
+ } else if (node.getAttribute('dimension')) {
+ dim = readNonNegativeIntegerString(node.getAttribute('dimension'));
+ } else if (
+ /** @type {Element} */ (node.parentNode).getAttribute('srsDimension')
+ ) {
+ dim = readNonNegativeIntegerString(
+ /** @type {Element} */ (node.parentNode).getAttribute('srsDimension')
+ );
+ } else if (contextDimension) {
+ dim = readNonNegativeIntegerString(contextDimension);
+ }
+ let x, y, z;
+ const flatCoordinates = [];
+ for (let i = 0, ii = coords.length; i < ii; i += dim) {
+ x = parseFloat(coords[i]);
+ y = parseFloat(coords[i + 1]);
+ z = dim === 3 ? parseFloat(coords[i + 2]) : 0;
+ if (axisOrientation.substr(0, 2) === 'en') {
+ flatCoordinates.push(x, y, z);
+ } else {
+ flatCoordinates.push(y, x, z);
+ }
+ }
+ return flatCoordinates;
+ }
+
+ /**
+ * @param {Element} node Node.
+ * @param {import("../geom/Point.js").default} value Point geometry.
+ * @param {Array<*>} objectStack Node stack.
+ * @private
+ */
+ writePos_(node, value, objectStack) {
+ const context = objectStack[objectStack.length - 1];
+ const hasZ = context['hasZ'];
+ const srsDimension = hasZ ? '3' : '2';
+ node.setAttribute('srsDimension', srsDimension);
+ const srsName = context['srsName'];
+ let axisOrientation = 'enu';
+ if (srsName) {
+ axisOrientation = getProjection(srsName).getAxisOrientation();
+ }
+ const point = value.getCoordinates();
+ let coords;
+ // only 2d for simple features profile
+ if (axisOrientation.substr(0, 2) === 'en') {
+ coords = point[0] + ' ' + point[1];
+ } else {
+ coords = point[1] + ' ' + point[0];
+ }
+ if (hasZ) {
+ // For newly created points, Z can be undefined.
+ const z = point[2] || 0;
+ coords += ' ' + z;
+ }
+ writeStringTextNode(node, coords);
+ }
+
+ /**
+ * @param {Array} point Point geometry.
+ * @param {string=} opt_srsName Optional srsName
+ * @param {boolean=} opt_hasZ whether the geometry has a Z coordinate (is 3D) or not.
+ * @return {string} The coords string.
+ * @private
+ */
+ getCoords_(point, opt_srsName, opt_hasZ) {
+ let axisOrientation = 'enu';
+ if (opt_srsName) {
+ axisOrientation = getProjection(opt_srsName).getAxisOrientation();
+ }
+ let coords =
+ axisOrientation.substr(0, 2) === 'en'
+ ? point[0] + ' ' + point[1]
+ : point[1] + ' ' + point[0];
+ if (opt_hasZ) {
+ // For newly created points, Z can be undefined.
+ const z = point[2] || 0;
+ coords += ' ' + z;
+ }
+
+ return coords;
+ }
+
+ /**
+ * @param {Element} node Node.
+ * @param {LineString|import("../geom/LinearRing.js").default} value Geometry.
+ * @param {Array<*>} objectStack Node stack.
+ * @private
+ */
+ writePosList_(node, value, objectStack) {
+ const context = objectStack[objectStack.length - 1];
+ const hasZ = context['hasZ'];
+ const srsDimension = hasZ ? '3' : '2';
+ node.setAttribute('srsDimension', srsDimension);
+ const srsName = context['srsName'];
+ // only 2d for simple features profile
+ const points = value.getCoordinates();
+ const len = points.length;
+ const parts = new Array(len);
+ let point;
+ for (let i = 0; i < len; ++i) {
+ point = points[i];
+ parts[i] = this.getCoords_(point, srsName, hasZ);
+ }
+ writeStringTextNode(node, parts.join(' '));
+ }
+
+ /**
+ * @param {Element} node Node.
+ * @param {import("../geom/Point.js").default} geometry Point geometry.
+ * @param {Array<*>} objectStack Node stack.
+ */
+ writePoint(node, geometry, objectStack) {
+ const context = objectStack[objectStack.length - 1];
+ const srsName = context['srsName'];
+ if (srsName) {
+ node.setAttribute('srsName', srsName);
+ }
+ const pos = createElementNS(node.namespaceURI, 'pos');
+ node.appendChild(pos);
+ this.writePos_(pos, geometry, objectStack);
+ }
+
+ /**
+ * @param {Element} node Node.
+ * @param {import("../extent.js").Extent} extent Extent.
+ * @param {Array<*>} objectStack Node stack.
+ */
+ writeEnvelope(node, extent, objectStack) {
+ const context = objectStack[objectStack.length - 1];
+ const srsName = context['srsName'];
+ if (srsName) {
+ node.setAttribute('srsName', srsName);
+ }
+ const keys = ['lowerCorner', 'upperCorner'];
+ const values = [extent[0] + ' ' + extent[1], extent[2] + ' ' + extent[3]];
+ pushSerializeAndPop(
+ /** @type {import("../xml.js").NodeStackItem} */
+ ({node: node}),
+ this.ENVELOPE_SERIALIZERS,
+ OBJECT_PROPERTY_NODE_FACTORY,
+ values,
+ objectStack,
+ keys,
+ this
+ );
+ }
+
+ /**
+ * @param {Element} node Node.
+ * @param {import("../geom/LinearRing.js").default} geometry LinearRing geometry.
+ * @param {Array<*>} objectStack Node stack.
+ */
+ writeLinearRing(node, geometry, objectStack) {
+ const context = objectStack[objectStack.length - 1];
+ const srsName = context['srsName'];
+ if (srsName) {
+ node.setAttribute('srsName', srsName);
+ }
+ const posList = createElementNS(node.namespaceURI, 'posList');
+ node.appendChild(posList);
+ this.writePosList_(posList, geometry, objectStack);
+ }
+
+ /**
+ * @param {*} value Value.
+ * @param {Array<*>} objectStack Object stack.
+ * @param {string=} opt_nodeName Node name.
+ * @return {Node} Node.
+ * @private
+ */
+ RING_NODE_FACTORY_(value, objectStack, opt_nodeName) {
+ const context = objectStack[objectStack.length - 1];
+ const parentNode = context.node;
+ const exteriorWritten = context['exteriorWritten'];
+ if (exteriorWritten === undefined) {
+ context['exteriorWritten'] = true;
+ }
+ return createElementNS(
+ parentNode.namespaceURI,
+ exteriorWritten !== undefined ? 'interior' : 'exterior'
+ );
+ }
+
+ /**
+ * @param {Element} node Node.
+ * @param {Polygon} geometry Polygon geometry.
+ * @param {Array<*>} objectStack Node stack.
+ */
+ writeSurfaceOrPolygon(node, geometry, objectStack) {
+ const context = objectStack[objectStack.length - 1];
+ const hasZ = context['hasZ'];
+ const srsName = context['srsName'];
+ if (node.nodeName !== 'PolygonPatch' && srsName) {
+ node.setAttribute('srsName', srsName);
+ }
+ if (node.nodeName === 'Polygon' || node.nodeName === 'PolygonPatch') {
+ const rings = geometry.getLinearRings();
+ pushSerializeAndPop(
+ {node: node, hasZ: hasZ, srsName: srsName},
+ this.RING_SERIALIZERS,
+ this.RING_NODE_FACTORY_,
+ rings,
+ objectStack,
+ undefined,
+ this
+ );
+ } else if (node.nodeName === 'Surface') {
+ const patches = createElementNS(node.namespaceURI, 'patches');
+ node.appendChild(patches);
+ this.writeSurfacePatches_(patches, geometry, objectStack);
+ }
+ }
+
+ /**
+ * @param {Element} node Node.
+ * @param {LineString} geometry LineString geometry.
+ * @param {Array<*>} objectStack Node stack.
+ */
+ writeCurveOrLineString(node, geometry, objectStack) {
+ const context = objectStack[objectStack.length - 1];
+ const srsName = context['srsName'];
+ if (node.nodeName !== 'LineStringSegment' && srsName) {
+ node.setAttribute('srsName', srsName);
+ }
+ if (
+ node.nodeName === 'LineString' ||
+ node.nodeName === 'LineStringSegment'
+ ) {
+ const posList = createElementNS(node.namespaceURI, 'posList');
+ node.appendChild(posList);
+ this.writePosList_(posList, geometry, objectStack);
+ } else if (node.nodeName === 'Curve') {
+ const segments = createElementNS(node.namespaceURI, 'segments');
+ node.appendChild(segments);
+ this.writeCurveSegments_(segments, geometry, objectStack);
+ }
+ }
+
+ /**
+ * @param {Element} node Node.
+ * @param {MultiPolygon} geometry MultiPolygon geometry.
+ * @param {Array<*>} objectStack Node stack.
+ */
+ writeMultiSurfaceOrPolygon(node, geometry, objectStack) {
+ const context = objectStack[objectStack.length - 1];
+ const hasZ = context['hasZ'];
+ const srsName = context['srsName'];
+ const surface = context['surface'];
+ if (srsName) {
+ node.setAttribute('srsName', srsName);
+ }
+ const polygons = geometry.getPolygons();
+ pushSerializeAndPop(
+ {node: node, hasZ: hasZ, srsName: srsName, surface: surface},
+ this.SURFACEORPOLYGONMEMBER_SERIALIZERS,
+ this.MULTIGEOMETRY_MEMBER_NODE_FACTORY_,
+ polygons,
+ objectStack,
+ undefined,
+ this
+ );
+ }
+
+ /**
+ * @param {Element} node Node.
+ * @param {import("../geom/MultiPoint.js").default} geometry MultiPoint geometry.
+ * @param {Array<*>} objectStack Node stack.
+ */
+ writeMultiPoint(node, geometry, objectStack) {
+ const context = objectStack[objectStack.length - 1];
+ const srsName = context['srsName'];
+ const hasZ = context['hasZ'];
+ if (srsName) {
+ node.setAttribute('srsName', srsName);
+ }
+ const points = geometry.getPoints();
+ pushSerializeAndPop(
+ {node: node, hasZ: hasZ, srsName: srsName},
+ this.POINTMEMBER_SERIALIZERS,
+ makeSimpleNodeFactory('pointMember'),
+ points,
+ objectStack,
+ undefined,
+ this
+ );
+ }
+
+ /**
+ * @param {Element} node Node.
+ * @param {MultiLineString} geometry MultiLineString geometry.
+ * @param {Array<*>} objectStack Node stack.
+ */
+ writeMultiCurveOrLineString(node, geometry, objectStack) {
+ const context = objectStack[objectStack.length - 1];
+ const hasZ = context['hasZ'];
+ const srsName = context['srsName'];
+ const curve = context['curve'];
+ if (srsName) {
+ node.setAttribute('srsName', srsName);
+ }
+ const lines = geometry.getLineStrings();
+ pushSerializeAndPop(
+ {node: node, hasZ: hasZ, srsName: srsName, curve: curve},
+ this.LINESTRINGORCURVEMEMBER_SERIALIZERS,
+ this.MULTIGEOMETRY_MEMBER_NODE_FACTORY_,
+ lines,
+ objectStack,
+ undefined,
+ this
+ );
+ }
+
+ /**
+ * @param {Node} node Node.
+ * @param {import("../geom/LinearRing.js").default} ring LinearRing geometry.
+ * @param {Array<*>} objectStack Node stack.
+ */
+ writeRing(node, ring, objectStack) {
+ const linearRing = createElementNS(node.namespaceURI, 'LinearRing');
+ node.appendChild(linearRing);
+ this.writeLinearRing(linearRing, ring, objectStack);
+ }
+
+ /**
+ * @param {Node} node Node.
+ * @param {Polygon} polygon Polygon geometry.
+ * @param {Array<*>} objectStack Node stack.
+ */
+ writeSurfaceOrPolygonMember(node, polygon, objectStack) {
+ const child = this.GEOMETRY_NODE_FACTORY_(polygon, objectStack);
+ if (child) {
+ node.appendChild(child);
+ this.writeSurfaceOrPolygon(child, polygon, objectStack);
+ }
+ }
+
+ /**
+ * @param {Node} node Node.
+ * @param {import("../geom/Point.js").default} point Point geometry.
+ * @param {Array<*>} objectStack Node stack.
+ */
+ writePointMember(node, point, objectStack) {
+ const child = createElementNS(node.namespaceURI, 'Point');
+ node.appendChild(child);
+ this.writePoint(child, point, objectStack);
+ }
+
+ /**
+ * @param {Node} node Node.
+ * @param {LineString} line LineString geometry.
+ * @param {Array<*>} objectStack Node stack.
+ */
+ writeLineStringOrCurveMember(node, line, objectStack) {
+ const child = this.GEOMETRY_NODE_FACTORY_(line, objectStack);
+ if (child) {
+ node.appendChild(child);
+ this.writeCurveOrLineString(child, line, objectStack);
+ }
+ }
+
+ /**
+ * @param {Node} node Node.
+ * @param {Polygon} polygon Polygon geometry.
+ * @param {Array<*>} objectStack Node stack.
+ * @private
+ */
+ writeSurfacePatches_(node, polygon, objectStack) {
+ const child = createElementNS(node.namespaceURI, 'PolygonPatch');
+ node.appendChild(child);
+ this.writeSurfaceOrPolygon(child, polygon, objectStack);
+ }
+
+ /**
+ * @param {Node} node Node.
+ * @param {LineString} line LineString geometry.
+ * @param {Array<*>} objectStack Node stack.
+ * @private
+ */
+ writeCurveSegments_(node, line, objectStack) {
+ const child = createElementNS(node.namespaceURI, 'LineStringSegment');
+ node.appendChild(child);
+ this.writeCurveOrLineString(child, line, objectStack);
+ }
+
+ /**
+ * @param {Node} node Node.
+ * @param {import("../geom/Geometry.js").default|import("../extent.js").Extent} geometry Geometry.
+ * @param {Array<*>} objectStack Node stack.
+ */
+ writeGeometryElement(node, geometry, objectStack) {
+ const context = /** @type {import("./Feature.js").WriteOptions} */ (objectStack[
+ objectStack.length - 1
+ ]);
+ const item = assign({}, context);
+ item['node'] = node;
+ let value;
+ if (Array.isArray(geometry)) {
+ value = transformExtentWithOptions(
+ /** @type {import("../extent.js").Extent} */ (geometry),
+ context
+ );
+ } else {
+ value = transformGeometryWithOptions(
+ /** @type {import("../geom/Geometry.js").default} */ (geometry),
+ true,
+ context
+ );
+ }
+ pushSerializeAndPop(
+ /** @type {import("../xml.js").NodeStackItem} */
+ (item),
+ this.GEOMETRY_SERIALIZERS,
+ this.GEOMETRY_NODE_FACTORY_,
+ [value],
+ objectStack,
+ undefined,
+ this
+ );
+ }
+
+ /**
+ * @param {Element} node Node.
+ * @param {import("../Feature.js").default} feature Feature.
+ * @param {Array<*>} objectStack Node stack.
+ */
+ writeFeatureElement(node, feature, objectStack) {
+ const fid = feature.getId();
+ if (fid) {
+ node.setAttribute('fid', /** @type {string} */ (fid));
+ }
+ const context = /** @type {Object} */ (objectStack[objectStack.length - 1]);
+ const featureNS = context['featureNS'];
+ const geometryName = feature.getGeometryName();
+ if (!context.serializers) {
+ context.serializers = {};
+ context.serializers[featureNS] = {};
+ }
+ const keys = [];
+ const values = [];
+ if (feature.hasProperties()) {
+ const properties = feature.getProperties();
+ for (const key in properties) {
+ const value = properties[key];
+ if (value !== null) {
+ keys.push(key);
+ values.push(value);
+ if (
+ key == geometryName ||
+ typeof (/** @type {?} */ (value).getSimplifiedGeometry) ===
+ 'function'
+ ) {
+ if (!(key in context.serializers[featureNS])) {
+ context.serializers[featureNS][key] = makeChildAppender(
+ this.writeGeometryElement,
+ this
+ );
+ }
+ } else {
+ if (!(key in context.serializers[featureNS])) {
+ context.serializers[featureNS][key] = makeChildAppender(
+ writeStringTextNode
+ );
+ }
+ }
+ }
+ }
+ }
+ const item = assign({}, context);
+ item.node = node;
+ pushSerializeAndPop(
+ /** @type {import("../xml.js").NodeStackItem} */
+ (item),
+ context.serializers,
+ makeSimpleNodeFactory(undefined, featureNS),
+ values,
+ objectStack,
+ keys
+ );
+ }
+
+ /**
+ * @param {Node} node Node.
+ * @param {Array} features Features.
+ * @param {Array<*>} objectStack Node stack.
+ * @private
+ */
+ writeFeatureMembers_(node, features, objectStack) {
+ const context = /** @type {Object} */ (objectStack[objectStack.length - 1]);
+ const featureType = context['featureType'];
+ const featureNS = context['featureNS'];
+ /** @type {Object>} */
+ const serializers = {};
+ serializers[featureNS] = {};
+ serializers[featureNS][featureType] = makeChildAppender(
+ this.writeFeatureElement,
+ this
+ );
+ const item = assign({}, context);
+ item.node = node;
+ pushSerializeAndPop(
+ /** @type {import("../xml.js").NodeStackItem} */
+ (item),
+ serializers,
+ makeSimpleNodeFactory(featureType, featureNS),
+ features,
+ objectStack
+ );
+ }
+
+ /**
+ * @const
+ * @param {*} value Value.
+ * @param {Array<*>} objectStack Object stack.
+ * @param {string=} opt_nodeName Node name.
+ * @return {Node|undefined} Node.
+ * @private
+ */
+ MULTIGEOMETRY_MEMBER_NODE_FACTORY_(value, objectStack, opt_nodeName) {
+ const parentNode = objectStack[objectStack.length - 1].node;
+ return createElementNS(
+ this.namespace,
+ MULTIGEOMETRY_TO_MEMBER_NODENAME[parentNode.nodeName]
+ );
+ }
+
+ /**
+ * @const
+ * @param {*} value Value.
+ * @param {Array<*>} objectStack Object stack.
+ * @param {string=} opt_nodeName Node name.
+ * @return {Element|undefined} Node.
+ * @private
+ */
+ GEOMETRY_NODE_FACTORY_(value, objectStack, opt_nodeName) {
+ const context = objectStack[objectStack.length - 1];
+ const multiSurface = context['multiSurface'];
+ const surface = context['surface'];
+ const curve = context['curve'];
+ const multiCurve = context['multiCurve'];
+ let nodeName;
+ if (!Array.isArray(value)) {
+ nodeName = /** @type {import("../geom/Geometry.js").default} */ (value).getType();
+ if (nodeName === 'MultiPolygon' && multiSurface === true) {
+ nodeName = 'MultiSurface';
+ } else if (nodeName === 'Polygon' && surface === true) {
+ nodeName = 'Surface';
+ } else if (nodeName === 'LineString' && curve === true) {
+ nodeName = 'Curve';
+ } else if (nodeName === 'MultiLineString' && multiCurve === true) {
+ nodeName = 'MultiCurve';
+ }
+ } else {
+ nodeName = 'Envelope';
+ }
+ return createElementNS(this.namespace, nodeName);
+ }
+
+ /**
+ * Encode a geometry in GML 3.1.1 Simple Features.
+ *
+ * @param {import("../geom/Geometry.js").default} geometry Geometry.
+ * @param {import("./Feature.js").WriteOptions=} opt_options Options.
+ * @return {Node} Node.
+ * @api
+ */
+ writeGeometryNode(geometry, opt_options) {
+ opt_options = this.adaptOptions(opt_options);
+ const geom = createElementNS(this.namespace, 'geom');
+ const context = {
+ node: geom,
+ hasZ: this.hasZ,
+ srsName: this.srsName,
+ curve: this.curve_,
+ surface: this.surface_,
+ multiSurface: this.multiSurface_,
+ multiCurve: this.multiCurve_,
+ };
+ if (opt_options) {
+ assign(context, opt_options);
+ }
+ this.writeGeometryElement(geom, geometry, [context]);
+ return geom;
+ }
+
+ /**
+ * Encode an array of features in the GML 3.1.1 format as an XML node.
+ *
+ * @param {Array} features Features.
+ * @param {import("./Feature.js").WriteOptions=} opt_options Options.
+ * @return {Element} Node.
+ * @api
+ */
+ writeFeaturesNode(features, opt_options) {
+ opt_options = this.adaptOptions(opt_options);
+ const node = createElementNS(this.namespace, 'featureMembers');
+ node.setAttributeNS(
+ XML_SCHEMA_INSTANCE_URI,
+ 'xsi:schemaLocation',
+ this.schemaLocation
+ );
+ const context = {
+ srsName: this.srsName,
+ hasZ: this.hasZ,
+ curve: this.curve_,
+ surface: this.surface_,
+ multiSurface: this.multiSurface_,
+ multiCurve: this.multiCurve_,
+ featureNS: this.featureNS,
+ featureType: this.featureType,
+ };
+ if (opt_options) {
+ assign(context, opt_options);
+ }
+ this.writeFeatureMembers_(node, features, [context]);
+ return node;
+ }
+}
+
+/**
+ * @const
+ * @type {Object>}
+ */
+GML3.prototype.GEOMETRY_FLAT_COORDINATES_PARSERS = {
+ 'http://www.opengis.net/gml': {
+ 'pos': makeReplacer(GML3.prototype.readFlatPos),
+ 'posList': makeReplacer(GML3.prototype.readFlatPosList),
+ 'coordinates': makeReplacer(GML2.prototype.readFlatCoordinates),
+ },
+};
+
+/**
+ * @const
+ * @type {Object>}
+ */
+GML3.prototype.FLAT_LINEAR_RINGS_PARSERS = {
+ 'http://www.opengis.net/gml': {
+ 'interior': GML3.prototype.interiorParser,
+ 'exterior': GML3.prototype.exteriorParser,
+ },
+};
+
+/**
+ * @const
+ * @type {Object>}
+ */
+GML3.prototype.GEOMETRY_PARSERS = {
+ 'http://www.opengis.net/gml': {
+ 'Point': makeReplacer(GMLBase.prototype.readPoint),
+ 'MultiPoint': makeReplacer(GMLBase.prototype.readMultiPoint),
+ 'LineString': makeReplacer(GMLBase.prototype.readLineString),
+ 'MultiLineString': makeReplacer(GMLBase.prototype.readMultiLineString),
+ 'LinearRing': makeReplacer(GMLBase.prototype.readLinearRing),
+ 'Polygon': makeReplacer(GMLBase.prototype.readPolygon),
+ 'MultiPolygon': makeReplacer(GMLBase.prototype.readMultiPolygon),
+ 'Surface': makeReplacer(GML3.prototype.readSurface),
+ 'MultiSurface': makeReplacer(GML3.prototype.readMultiSurface),
+ 'Curve': makeReplacer(GML3.prototype.readCurve),
+ 'MultiCurve': makeReplacer(GML3.prototype.readMultiCurve),
+ 'Envelope': makeReplacer(GML3.prototype.readEnvelope),
+ },
+};
+
+/**
+ * @const
+ * @type {Object>}
+ */
+GML3.prototype.MULTICURVE_PARSERS = {
+ 'http://www.opengis.net/gml': {
+ 'curveMember': makeArrayPusher(GML3.prototype.curveMemberParser),
+ 'curveMembers': makeArrayPusher(GML3.prototype.curveMemberParser),
+ },
+};
+
+/**
+ * @const
+ * @type {Object>}
+ */
+GML3.prototype.MULTISURFACE_PARSERS = {
+ 'http://www.opengis.net/gml': {
+ 'surfaceMember': makeArrayPusher(GML3.prototype.surfaceMemberParser),
+ 'surfaceMembers': makeArrayPusher(GML3.prototype.surfaceMemberParser),
+ },
+};
+
+/**
+ * @const
+ * @type {Object>}
+ */
+GML3.prototype.CURVEMEMBER_PARSERS = {
+ 'http://www.opengis.net/gml': {
+ 'LineString': makeArrayPusher(GMLBase.prototype.readLineString),
+ 'Curve': makeArrayPusher(GML3.prototype.readCurve),
+ },
+};
+
+/**
+ * @const
+ * @type {Object>}
+ */
+GML3.prototype.SURFACEMEMBER_PARSERS = {
+ 'http://www.opengis.net/gml': {
+ 'Polygon': makeArrayPusher(GMLBase.prototype.readPolygon),
+ 'Surface': makeArrayPusher(GML3.prototype.readSurface),
+ },
+};
+
+/**
+ * @const
+ * @type {Object>}
+ */
+GML3.prototype.SURFACE_PARSERS = {
+ 'http://www.opengis.net/gml': {
+ 'patches': makeReplacer(GML3.prototype.readPatch),
+ },
+};
+
+/**
+ * @const
+ * @type {Object>}
+ */
+GML3.prototype.CURVE_PARSERS = {
+ 'http://www.opengis.net/gml': {
+ 'segments': makeReplacer(GML3.prototype.readSegment),
+ },
+};
+
+/**
+ * @const
+ * @type {Object>}
+ */
+GML3.prototype.ENVELOPE_PARSERS = {
+ 'http://www.opengis.net/gml': {
+ 'lowerCorner': makeArrayPusher(GML3.prototype.readFlatPosList),
+ 'upperCorner': makeArrayPusher(GML3.prototype.readFlatPosList),
+ },
+};
+
+/**
+ * @const
+ * @type {Object>}
+ */
+GML3.prototype.PATCHES_PARSERS = {
+ 'http://www.opengis.net/gml': {
+ 'PolygonPatch': makeReplacer(GML3.prototype.readPolygonPatch),
+ },
+};
+
+/**
+ * @const
+ * @type {Object>}
+ */
+GML3.prototype.SEGMENTS_PARSERS = {
+ 'http://www.opengis.net/gml': {
+ 'LineStringSegment': makeReplacer(GML3.prototype.readLineStringSegment),
+ },
+};
+
+/**
+ * Encode an array of features in GML 3.1.1 Simple Features.
+ *
+ * @function
+ * @param {Array} features Features.
+ * @param {import("./Feature.js").WriteOptions=} opt_options Options.
+ * @return {string} Result.
+ * @api
+ */
+GML3.prototype.writeFeatures;
+
+/**
+ * @type {Object>}
+ */
+GML3.prototype.RING_SERIALIZERS = {
+ 'http://www.opengis.net/gml': {
+ 'exterior': makeChildAppender(GML3.prototype.writeRing),
+ 'interior': makeChildAppender(GML3.prototype.writeRing),
+ },
+};
+
+/**
+ * @type {Object>}
+ */
+GML3.prototype.ENVELOPE_SERIALIZERS = {
+ 'http://www.opengis.net/gml': {
+ 'lowerCorner': makeChildAppender(writeStringTextNode),
+ 'upperCorner': makeChildAppender(writeStringTextNode),
+ },
+};
+
+/**
+ * @type {Object>}
+ */
+GML3.prototype.SURFACEORPOLYGONMEMBER_SERIALIZERS = {
+ 'http://www.opengis.net/gml': {
+ 'surfaceMember': makeChildAppender(
+ GML3.prototype.writeSurfaceOrPolygonMember
+ ),
+ 'polygonMember': makeChildAppender(
+ GML3.prototype.writeSurfaceOrPolygonMember
+ ),
+ },
+};
+
+/**
+ * @type {Object>}
+ */
+GML3.prototype.POINTMEMBER_SERIALIZERS = {
+ 'http://www.opengis.net/gml': {
+ 'pointMember': makeChildAppender(GML3.prototype.writePointMember),
+ },
+};
+
+/**
+ * @type {Object>}
+ */
+GML3.prototype.LINESTRINGORCURVEMEMBER_SERIALIZERS = {
+ 'http://www.opengis.net/gml': {
+ 'lineStringMember': makeChildAppender(
+ GML3.prototype.writeLineStringOrCurveMember
+ ),
+ 'curveMember': makeChildAppender(
+ GML3.prototype.writeLineStringOrCurveMember
+ ),
+ },
+};
+
+/**
+ * @type {Object>}
+ */
+GML3.prototype.GEOMETRY_SERIALIZERS = {
+ 'http://www.opengis.net/gml': {
+ 'Curve': makeChildAppender(GML3.prototype.writeCurveOrLineString),
+ 'MultiCurve': makeChildAppender(GML3.prototype.writeMultiCurveOrLineString),
+ 'Point': makeChildAppender(GML3.prototype.writePoint),
+ 'MultiPoint': makeChildAppender(GML3.prototype.writeMultiPoint),
+ 'LineString': makeChildAppender(GML3.prototype.writeCurveOrLineString),
+ 'MultiLineString': makeChildAppender(
+ GML3.prototype.writeMultiCurveOrLineString
+ ),
+ 'LinearRing': makeChildAppender(GML3.prototype.writeLinearRing),
+ 'Polygon': makeChildAppender(GML3.prototype.writeSurfaceOrPolygon),
+ 'MultiPolygon': makeChildAppender(
+ GML3.prototype.writeMultiSurfaceOrPolygon
+ ),
+ 'Surface': makeChildAppender(GML3.prototype.writeSurfaceOrPolygon),
+ 'MultiSurface': makeChildAppender(
+ GML3.prototype.writeMultiSurfaceOrPolygon
+ ),
+ 'Envelope': makeChildAppender(GML3.prototype.writeEnvelope),
+ },
+};
+
+export default GML3;
\ No newline at end of file
diff --git a/src/common/format/GML32.js b/src/common/format/GML32.js
new file mode 100644
index 000000000..0b8a60c5e
--- /dev/null
+++ b/src/common/format/GML32.js
@@ -0,0 +1,336 @@
+/**
+ * @module ol/format/GML32
+ */
+import GML2 from './GML2.js';
+import GML3 from './GML32.js';
+import GMLBase from './GMLBase.js';
+import {makeArrayPusher, makeChildAppender, makeReplacer} from '../xml.js';
+import {writeStringTextNode} from '../format/xsd.js';
+
+/**
+ * @classdesc Feature format for reading and writing data in the GML format
+ * version 3.2.1.
+ * @api
+ */
+class GML32 extends GML3 {
+ /**
+ * @param {import("./GMLBase.js").Options=} opt_options Optional configuration object.
+ */
+ constructor(opt_options) {
+ const options = /** @type {import("./GMLBase.js").Options} */ (opt_options
+ ? opt_options
+ : {});
+
+ super(options);
+
+ /**
+ * @type {string}
+ */
+ this.schemaLocation = options.schemaLocation
+ ? options.schemaLocation
+ : this.namespace + ' http://schemas.opengis.net/gml/3.2.1/gml.xsd';
+ }
+}
+
+GML32.prototype.namespace = 'http://www.opengis.net/gml/3.2';
+
+/**
+ * @const
+ * @type {Object>}
+ */
+GML32.prototype.GEOMETRY_FLAT_COORDINATES_PARSERS = {
+ 'http://www.opengis.net/gml/3.2': {
+ 'pos': makeReplacer(GML3.prototype.readFlatPos),
+ 'posList': makeReplacer(GML3.prototype.readFlatPosList),
+ 'coordinates': makeReplacer(GML2.prototype.readFlatCoordinates),
+ },
+};
+
+/**
+ * @const
+ * @type {Object>}
+ */
+GML32.prototype.FLAT_LINEAR_RINGS_PARSERS = {
+ 'http://www.opengis.net/gml/3.2': {
+ 'interior': GML3.prototype.interiorParser,
+ 'exterior': GML3.prototype.exteriorParser,
+ },
+};
+
+/**
+ * @const
+ * @type {Object>}
+ */
+GML32.prototype.GEOMETRY_PARSERS = {
+ 'http://www.opengis.net/gml/3.2': {
+ 'Point': makeReplacer(GMLBase.prototype.readPoint),
+ 'MultiPoint': makeReplacer(GMLBase.prototype.readMultiPoint),
+ 'LineString': makeReplacer(GMLBase.prototype.readLineString),
+ 'MultiLineString': makeReplacer(GMLBase.prototype.readMultiLineString),
+ 'LinearRing': makeReplacer(GMLBase.prototype.readLinearRing),
+ 'Polygon': makeReplacer(GMLBase.prototype.readPolygon),
+ 'MultiPolygon': makeReplacer(GMLBase.prototype.readMultiPolygon),
+ 'Surface': makeReplacer(GML32.prototype.readSurface),
+ 'MultiSurface': makeReplacer(GML3.prototype.readMultiSurface),
+ 'Curve': makeReplacer(GML32.prototype.readCurve),
+ 'MultiCurve': makeReplacer(GML3.prototype.readMultiCurve),
+ 'Envelope': makeReplacer(GML32.prototype.readEnvelope),
+ },
+};
+
+/**
+ * @const
+ * @type {Object>}
+ */
+GML32.prototype.MULTICURVE_PARSERS = {
+ 'http://www.opengis.net/gml/3.2': {
+ 'curveMember': makeArrayPusher(GML3.prototype.curveMemberParser),
+ 'curveMembers': makeArrayPusher(GML3.prototype.curveMemberParser),
+ },
+};
+
+/**
+ * @const
+ * @type {Object>}
+ */
+GML32.prototype.MULTISURFACE_PARSERS = {
+ 'http://www.opengis.net/gml/3.2': {
+ 'surfaceMember': makeArrayPusher(GML3.prototype.surfaceMemberParser),
+ 'surfaceMembers': makeArrayPusher(GML3.prototype.surfaceMemberParser),
+ },
+};
+
+/**
+ * @const
+ * @type {Object>}
+ */
+GML32.prototype.CURVEMEMBER_PARSERS = {
+ 'http://www.opengis.net/gml/3.2': {
+ 'LineString': makeArrayPusher(GMLBase.prototype.readLineString),
+ 'Curve': makeArrayPusher(GML3.prototype.readCurve),
+ },
+};
+
+/**
+ * @const
+ * @type {Object>}
+ */
+GML32.prototype.SURFACEMEMBER_PARSERS = {
+ 'http://www.opengis.net/gml/3.2': {
+ 'Polygon': makeArrayPusher(GMLBase.prototype.readPolygon),
+ 'Surface': makeArrayPusher(GML3.prototype.readSurface),
+ },
+};
+
+/**
+ * @const
+ * @type {Object>}
+ */
+GML32.prototype.SURFACE_PARSERS = {
+ 'http://www.opengis.net/gml/3.2': {
+ 'patches': makeReplacer(GML3.prototype.readPatch),
+ },
+};
+
+/**
+ * @const
+ * @type {Object>}
+ */
+GML32.prototype.CURVE_PARSERS = {
+ 'http://www.opengis.net/gml/3.2': {
+ 'segments': makeReplacer(GML3.prototype.readSegment),
+ },
+};
+
+/**
+ * @const
+ * @type {Object>}
+ */
+GML32.prototype.ENVELOPE_PARSERS = {
+ 'http://www.opengis.net/gml/3.2': {
+ 'lowerCorner': makeArrayPusher(GML3.prototype.readFlatPosList),
+ 'upperCorner': makeArrayPusher(GML3.prototype.readFlatPosList),
+ },
+};
+
+/**
+ * @const
+ * @type {Object>}
+ */
+GML32.prototype.PATCHES_PARSERS = {
+ 'http://www.opengis.net/gml/3.2': {
+ 'PolygonPatch': makeReplacer(GML3.prototype.readPolygonPatch),
+ },
+};
+
+/**
+ * @const
+ * @type {Object>}
+ */
+GML32.prototype.SEGMENTS_PARSERS = {
+ 'http://www.opengis.net/gml/3.2': {
+ 'LineStringSegment': makeReplacer(GML3.prototype.readLineStringSegment),
+ },
+};
+
+/**
+ * @const
+ * @type {Object>}
+ */
+GML32.prototype.MULTIPOINT_PARSERS = {
+ 'http://www.opengis.net/gml/3.2': {
+ 'pointMember': makeArrayPusher(GMLBase.prototype.pointMemberParser),
+ 'pointMembers': makeArrayPusher(GMLBase.prototype.pointMemberParser),
+ },
+};
+
+/**
+ * @const
+ * @type {Object>}
+ */
+GML32.prototype.MULTILINESTRING_PARSERS = {
+ 'http://www.opengis.net/gml/3.2': {
+ 'lineStringMember': makeArrayPusher(
+ GMLBase.prototype.lineStringMemberParser
+ ),
+ 'lineStringMembers': makeArrayPusher(
+ GMLBase.prototype.lineStringMemberParser
+ ),
+ },
+};
+
+/**
+ * @const
+ * @type {Object>}
+ */
+GML32.prototype.MULTIPOLYGON_PARSERS = {
+ 'http://www.opengis.net/gml/3.2': {
+ 'polygonMember': makeArrayPusher(GMLBase.prototype.polygonMemberParser),
+ 'polygonMembers': makeArrayPusher(GMLBase.prototype.polygonMemberParser),
+ },
+};
+
+/**
+ * @const
+ * @type {Object>}
+ */
+GML32.prototype.POINTMEMBER_PARSERS = {
+ 'http://www.opengis.net/gml/3.2': {
+ 'Point': makeArrayPusher(GMLBase.prototype.readFlatCoordinatesFromNode),
+ },
+};
+
+/**
+ * @const
+ * @type {Object>}
+ */
+GML32.prototype.LINESTRINGMEMBER_PARSERS = {
+ 'http://www.opengis.net/gml/3.2': {
+ 'LineString': makeArrayPusher(GMLBase.prototype.readLineString),
+ },
+};
+
+/**
+ * @const
+ * @type {Object>}
+ */
+GML32.prototype.POLYGONMEMBER_PARSERS = {
+ 'http://www.opengis.net/gml/3.2': {
+ 'Polygon': makeArrayPusher(GMLBase.prototype.readPolygon),
+ },
+};
+
+/**
+ * @const
+ * @type {Object>}
+ */
+GML32.prototype.RING_PARSERS = {
+ 'http://www.opengis.net/gml/3.2': {
+ 'LinearRing': makeReplacer(GMLBase.prototype.readFlatLinearRing),
+ },
+};
+
+/**
+ * @type {Object>}
+ */
+GML32.prototype.RING_SERIALIZERS = {
+ 'http://www.opengis.net/gml/3.2': {
+ 'exterior': makeChildAppender(GML3.prototype.writeRing),
+ 'interior': makeChildAppender(GML3.prototype.writeRing),
+ },
+};
+
+/**
+ * @type {Object>}
+ */
+GML32.prototype.ENVELOPE_SERIALIZERS = {
+ 'http://www.opengis.net/gml/3.2': {
+ 'lowerCorner': makeChildAppender(writeStringTextNode),
+ 'upperCorner': makeChildAppender(writeStringTextNode),
+ },
+};
+
+/**
+ * @type {Object>}
+ */
+GML32.prototype.SURFACEORPOLYGONMEMBER_SERIALIZERS = {
+ 'http://www.opengis.net/gml/3.2': {
+ 'surfaceMember': makeChildAppender(
+ GML3.prototype.writeSurfaceOrPolygonMember
+ ),
+ 'polygonMember': makeChildAppender(
+ GML3.prototype.writeSurfaceOrPolygonMember
+ ),
+ },
+};
+
+/**
+ * @type {Object>}
+ */
+GML32.prototype.POINTMEMBER_SERIALIZERS = {
+ 'http://www.opengis.net/gml/3.2': {
+ 'pointMember': makeChildAppender(GML3.prototype.writePointMember),
+ },
+};
+
+/**
+ * @type {Object>}
+ */
+GML32.prototype.LINESTRINGORCURVEMEMBER_SERIALIZERS = {
+ 'http://www.opengis.net/gml/3.2': {
+ 'lineStringMember': makeChildAppender(
+ GML3.prototype.writeLineStringOrCurveMember
+ ),
+ 'curveMember': makeChildAppender(
+ GML3.prototype.writeLineStringOrCurveMember
+ ),
+ },
+};
+
+/**
+ * @type {Object>}
+ */
+GML32.prototype.GEOMETRY_SERIALIZERS = {
+ 'http://www.opengis.net/gml/3.2': {
+ 'Curve': makeChildAppender(GML3.prototype.writeCurveOrLineString),
+ 'MultiCurve': makeChildAppender(GML3.prototype.writeMultiCurveOrLineString),
+ 'Point': makeChildAppender(GML32.prototype.writePoint),
+ 'MultiPoint': makeChildAppender(GML3.prototype.writeMultiPoint),
+ 'LineString': makeChildAppender(GML3.prototype.writeCurveOrLineString),
+ 'MultiLineString': makeChildAppender(
+ GML3.prototype.writeMultiCurveOrLineString
+ ),
+ 'LinearRing': makeChildAppender(GML3.prototype.writeLinearRing),
+ 'Polygon': makeChildAppender(GML3.prototype.writeSurfaceOrPolygon),
+ 'MultiPolygon': makeChildAppender(
+ GML3.prototype.writeMultiSurfaceOrPolygon
+ ),
+ 'Surface': makeChildAppender(GML3.prototype.writeSurfaceOrPolygon),
+ 'MultiSurface': makeChildAppender(
+ GML3.prototype.writeMultiSurfaceOrPolygon
+ ),
+ 'Envelope': makeChildAppender(GML3.prototype.writeEnvelope),
+ },
+};
+
+export default GML32;
\ No newline at end of file
diff --git a/src/common/format/GMLBase.js b/src/common/format/GMLBase.js
new file mode 100644
index 000000000..b28acebaa
--- /dev/null
+++ b/src/common/format/GMLBase.js
@@ -0,0 +1,680 @@
+/**
+ * @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 GeometryLayout from '../geom/GeometryLayout.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 {assign} from './util/obj.js';
+import {extend} from './util/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. The non-breaking
+ * space (0xa0) is explicitly included as IE doesn't include it in its
+ * definition of `\s`.
+ *
+ * Information from `goog.string.isEmptyOrWhitespace`: https://github.com/google/closure-library/blob/e877b1e/closure/goog/string/string.js#L156-L160
+ *
+ * @const
+ * @type {RegExp}
+ */
+const ONLY_WHITESPACE_RE = /^[\s\xa0]*$/;
+
+/**
+ * @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
+ */
+class GMLBase extends XMLFeature {
+ /**
+ * @param {Options=} opt_options Optional configuration object.
+ */
+ constructor(opt_options) {
+ super();
+
+ const options = /** @type {Options} */ (opt_options ? opt_options : {});
+
+ /**
+ * @protected
+ * @type {Array|string|undefined}
+ */
+ this.featureType = options.featureType;
+
+ /**
+ * @protected
+ * @type {Object|string|undefined}
+ */
+ this.featureNS = options.featureNS;
+
+ /**
+ * @protected
+ * @type {string}
+ */
+ 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),
+ };
+ }
+
+ /**
+ * @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 = node.childNodes[i];
+ if (child.nodeType === 1) {
+ const ft = child.nodeName.split(':').pop();
+ if (featureType.indexOf(ft) === -1) {
+ 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].indexOf(':') === -1
+ ? defaultPrefix
+ : featureTypes[i].split(':')[0];
+ 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.
+ */
+ readGeometryElement(node, objectStack) {
+ const context = /** @type {Object} */ (objectStack[0]);
+ context['srsName'] = node.firstElementChild.getAttribute('srsName');
+ context['srsDimension'] = node.firstElementChild.getAttribute(
+ 'srsDimension'
+ );
+ const geometry = pushParseAndPop(
+ null,
+ this.GEOMETRY_PARSERS,
+ node,
+ objectStack,
+ this
+ );
+ if (geometry) {
+ if (Array.isArray(geometry)) {
+ return transformExtentWithOptions(
+ /** @type {import("../extent.js").Extent} */ (geometry),
+ context
+ );
+ } else {
+ return transformGeometryWithOptions(
+ /** @type {import("../geom/Geometry.js").default} */ (geometry),
+ false,
+ context
+ );
+ }
+ } else {
+ return 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
+ value = 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;
+ }
+ }
+
+ if (values[localName]) {
+ if (!(values[localName] instanceof Array)) {
+ values[localName] = [values[localName]];
+ }
+ values[localName].push(value);
+ } else {
+ values[localName] = value;
+ }
+
+ const len = n.attributes.length;
+ if (len > 0) {
+ values[localName] = {_content_: values[localName]};
+ for (let i = 0; i < len; i++) {
+ const attName = n.attributes[i].name;
+ values[localName][attName] = n.attributes[i].value;
+ }
+ }
+ }
+ if (!asFeature) {
+ return values;
+ } else {
+ 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, GeometryLayout.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);
+ } else {
+ 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, GeometryLayout.XYZ);
+ return lineString;
+ } else {
+ 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;
+ } else {
+ 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, GeometryLayout.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, GeometryLayout.XYZ, ends);
+ } else {
+ 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=} opt_options Options.
+ * @protected
+ * @return {import("../geom/Geometry.js").default|import("../extent.js").Extent} Geometry.
+ */
+ //@ts-ignore
+ readGeometryFromNode(node, opt_options) {
+ const geometry = this.readGeometryElement(node, [
+ this.getReadOptions(node, opt_options ? opt_options : {}),
+ ]);
+ return geometry ? geometry : null;
+ }
+
+ /**
+ * @param {Element} node Node.
+ * @param {import("./Feature.js").ReadOptions=} opt_options Options.
+ * @return {Array} Features.
+ */
+ readFeaturesFromNode(node, opt_options) {
+ const options = {
+ featureType: this.featureType,
+ featureNS: this.featureNS,
+ };
+ if (opt_options) {
+ assign(options, this.getReadOptions(node, opt_options));
+ }
+ const features = this.readFeaturesInternal(node, [options]);
+ return features || [];
+ }
+
+ /**
+ * @param {Element} node Node.
+ * @return {import("../proj/Projection.js").default} Projection.
+ */
+ 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;
\ No newline at end of file
diff --git a/src/common/format/XML.js b/src/common/format/XML.js
new file mode 100644
index 000000000..a019a3bec
--- /dev/null
+++ b/src/common/format/XML.js
@@ -0,0 +1,54 @@
+/**
+ * @module ol/format/XML
+ */
+import {isDocument, parse} from './util/xml.js';
+
+/**
+ * @classdesc
+ * Generic format for reading non-feature XML data
+ *
+ * @abstract
+ */
+class XML {
+ /**
+ * Read the source document.
+ *
+ * @param {Document|Element|string} source The XML source.
+ * @return {Object} An object representing the source.
+ * @api
+ */
+ read(source) {
+ if (!source) {
+ return null;
+ } else if (typeof source === 'string') {
+ const doc = parse(source);
+ return this.readFromDocument(doc);
+ } else if (isDocument(source)) {
+ return this.readFromDocument(/** @type {Document} */ (source));
+ } else {
+ return this.readFromNode(/** @type {Element} */ (source));
+ }
+ }
+
+ /**
+ * @param {Document} doc Document.
+ * @return {Object} Object
+ */
+ readFromDocument(doc) {
+ for (let n = doc.firstChild; n; n = n.nextSibling) {
+ if (n.nodeType == Node.ELEMENT_NODE) {
+ return this.readFromNode(/** @type {Element} */ (n));
+ }
+ }
+ return null;
+ }
+
+ /**
+ * @abstract
+ * @param {Element} node Node.
+ * @return {Object} Object
+ */
+ readFromNode(node) {}
+}
+
+export default XML;
\ No newline at end of file
diff --git a/src/common/format/util/array.js b/src/common/format/util/array.js
new file mode 100644
index 000000000..3f7a03315
--- /dev/null
+++ b/src/common/format/util/array.js
@@ -0,0 +1,238 @@
+/**
+ * @module ol/array
+ */
+
+/**
+ * Performs a binary search on the provided sorted list and returns the index of the item if found. If it can't be found it'll return -1.
+ * https://github.com/darkskyapp/binary-search
+ *
+ * @param {Array<*>} haystack Items to search through.
+ * @param {*} needle The item to look for.
+ * @param {Function=} opt_comparator Comparator function.
+ * @return {number} The index of the item if found, -1 if not.
+ */
+export function binarySearch(haystack, needle, opt_comparator) {
+ let mid, cmp;
+ const comparator = opt_comparator || numberSafeCompareFunction;
+ let low = 0;
+ let high = haystack.length;
+ let found = false;
+
+ while (low < high) {
+ /* Note that "(low + high) >>> 1" may overflow, and results in a typecast
+ * to double (which gives the wrong results). */
+ mid = low + ((high - low) >> 1);
+ cmp = +comparator(haystack[mid], needle);
+
+ if (cmp < 0.0) {
+ /* Too low. */
+ low = mid + 1;
+ } else {
+ /* Key found or too high */
+ high = mid;
+ found = !cmp;
+ }
+ }
+
+ /* Key not found. */
+ return found ? low : ~low;
+ }
+
+ /**
+ * Compare function for array sort that is safe for numbers.
+ * @param {*} a The first object to be compared.
+ * @param {*} b The second object to be compared.
+ * @return {number} A negative number, zero, or a positive number as the first
+ * argument is less than, equal to, or greater than the second.
+ */
+ export function numberSafeCompareFunction(a, b) {
+ return a > b ? 1 : a < b ? -1 : 0;
+ }
+
+ /**
+ * Whether the array contains the given object.
+ * @param {Array<*>} arr The array to test for the presence of the element.
+ * @param {*} obj The object for which to test.
+ * @return {boolean} The object is in the array.
+ */
+ export function includes(arr, obj) {
+ return arr.indexOf(obj) >= 0;
+ }
+
+ /**
+ * @param {Array} arr Array.
+ * @param {number} target Target.
+ * @param {number} direction 0 means return the nearest, > 0
+ * means return the largest nearest, < 0 means return the
+ * smallest nearest.
+ * @return {number} Index.
+ */
+ export function linearFindNearest(arr, target, direction) {
+ const n = arr.length;
+ if (arr[0] <= target) {
+ return 0;
+ } else if (target <= arr[n - 1]) {
+ return n - 1;
+ } else {
+ let i;
+ if (direction > 0) {
+ for (i = 1; i < n; ++i) {
+ if (arr[i] < target) {
+ return i - 1;
+ }
+ }
+ } else if (direction < 0) {
+ for (i = 1; i < n; ++i) {
+ if (arr[i] <= target) {
+ return i;
+ }
+ }
+ } else {
+ for (i = 1; i < n; ++i) {
+ if (arr[i] == target) {
+ return i;
+ } else if (arr[i] < target) {
+ if (arr[i - 1] - target < target - arr[i]) {
+ return i - 1;
+ } else {
+ return i;
+ }
+ }
+ }
+ }
+ return n - 1;
+ }
+ }
+
+ /**
+ * @param {Array<*>} arr Array.
+ * @param {number} begin Begin index.
+ * @param {number} end End index.
+ */
+ export function reverseSubArray(arr, begin, end) {
+ while (begin < end) {
+ const tmp = arr[begin];
+ arr[begin] = arr[end];
+ arr[end] = tmp;
+ ++begin;
+ --end;
+ }
+ }
+
+ /**
+ * @param {Array} arr The array to modify.
+ * @param {!Array|VALUE} data The elements or arrays of elements to add to arr.
+ * @template VALUE
+ */
+ export function extend(arr, data) {
+ const extension = Array.isArray(data) ? data : [data];
+ const length = extension.length;
+ for (let i = 0; i < length; i++) {
+ arr[arr.length] = extension[i];
+ }
+ }
+
+ /**
+ * @param {Array} arr The array to modify.
+ * @param {VALUE} obj The element to remove.
+ * @template VALUE
+ * @return {boolean} If the element was removed.
+ */
+ export function remove(arr, obj) {
+ const i = arr.indexOf(obj);
+ const found = i > -1;
+ if (found) {
+ arr.splice(i, 1);
+ }
+ return found;
+ }
+
+ /**
+ * @param {Array} arr The array to search in.
+ * @param {function(VALUE, number, ?) : boolean} func The function to compare.
+ * @template VALUE
+ * @return {VALUE|null} The element found or null.
+ */
+ export function find(arr, func) {
+ const length = arr.length >>> 0;
+ let value;
+
+ for (let i = 0; i < length; i++) {
+ value = arr[i];
+ if (func(value, i, arr)) {
+ return value;
+ }
+ }
+ return null;
+ }
+
+ /**
+ * @param {Array|Uint8ClampedArray} arr1 The first array to compare.
+ * @param {Array|Uint8ClampedArray} arr2 The second array to compare.
+ * @return {boolean} Whether the two arrays are equal.
+ */
+ export function equals(arr1, arr2) {
+ const len1 = arr1.length;
+ if (len1 !== arr2.length) {
+ return false;
+ }
+ for (let i = 0; i < len1; i++) {
+ if (arr1[i] !== arr2[i]) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ /**
+ * Sort the passed array such that the relative order of equal elements is preverved.
+ * See https://en.wikipedia.org/wiki/Sorting_algorithm#Stability for details.
+ * @param {Array<*>} arr The array to sort (modifies original).
+ * @param {!function(*, *): number} compareFnc Comparison function.
+ * @api
+ */
+ export function stableSort(arr, compareFnc) {
+ const length = arr.length;
+ const tmp = Array(arr.length);
+ let i;
+ for (i = 0; i < length; i++) {
+ tmp[i] = {index: i, value: arr[i]};
+ }
+ tmp.sort(function (a, b) {
+ return compareFnc(a.value, b.value) || a.index - b.index;
+ });
+ for (i = 0; i < arr.length; i++) {
+ arr[i] = tmp[i].value;
+ }
+ }
+
+ /**
+ * @param {Array<*>} arr The array to search in.
+ * @param {Function} func Comparison function.
+ * @return {number} Return index.
+ */
+ export function findIndex(arr, func) {
+ let index;
+ const found = !arr.every(function (el, idx) {
+ index = idx;
+ return !func(el, idx, arr);
+ });
+ return found ? index : -1;
+ }
+
+ /**
+ * @param {Array<*>} arr The array to test.
+ * @param {Function=} opt_func Comparison function.
+ * @param {boolean=} opt_strict Strictly sorted (default false).
+ * @return {boolean} Return index.
+ */
+ export function isSorted(arr, opt_func, opt_strict) {
+ const compare = opt_func || numberSafeCompareFunction;
+ return arr.every(function (currentVal, index) {
+ if (index === 0) {
+ return true;
+ }
+ const res = compare(arr[index - 1], currentVal);
+ return !(res > 0 || (opt_strict && res === 0));
+ });
+ }
\ No newline at end of file
diff --git a/src/common/format/util/obj.js b/src/common/format/util/obj.js
new file mode 100644
index 000000000..114bd0099
--- /dev/null
+++ b/src/common/format/util/obj.js
@@ -0,0 +1,76 @@
+/**
+ * @module ol/obj
+ */
+
+/**
+ * Polyfill for Object.assign(). Assigns enumerable and own properties from
+ * one or more source objects to a target object.
+ * See https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_Objects/Object/assign.
+ *
+ * @param {!Object} target The target object.
+ * @param {...Object} var_sources The source object(s).
+ * @return {!Object} The modified target object.
+ */
+export const assign =
+ typeof Object.assign === 'function'
+ ? Object.assign
+ : function (target, var_sources) {
+ if (target === undefined || target === null) {
+ throw new TypeError('Cannot convert undefined or null to object');
+ }
+
+ const output = Object(target);
+ for (let i = 1, ii = arguments.length; i < ii; ++i) {
+ const source = arguments[i];
+ if (source !== undefined && source !== null) {
+ for (const key in source) {
+ if (source.hasOwnProperty(key)) {
+ output[key] = source[key];
+ }
+ }
+ }
+ }
+ return output;
+ };
+
+/**
+ * Removes all properties from an object.
+ * @param {Object} object The object to clear.
+ */
+export function clear(object) {
+ for (const property in object) {
+ delete object[property];
+ }
+}
+
+/**
+ * Polyfill for Object.values(). Get an array of property values from an object.
+ * See https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/values
+ *
+ * @param {!Object} object The object from which to get the values.
+ * @return {!Array} The property values.
+ * @template K,V
+ */
+export const getValues =
+ typeof Object.values === 'function'
+ ? Object.values
+ : function (object) {
+ const values = [];
+ for (const property in object) {
+ values.push(object[property]);
+ }
+ return values;
+ };
+
+/**
+ * Determine if an object has any properties.
+ * @param {Object} object The object to check.
+ * @return {boolean} The object is empty.
+ */
+export function isEmpty(object) {
+ let property;
+ for (property in object) {
+ return false;
+ }
+ return !property;
+}
\ No newline at end of file
diff --git a/src/common/format/util/xml.js b/src/common/format/util/xml.js
new file mode 100644
index 000000000..5aad47f4a
--- /dev/null
+++ b/src/common/format/util/xml.js
@@ -0,0 +1,591 @@
+/**
+ * @module ol/xml
+ */
+import {extend} from './array.js';
+
+/**
+ * When using {@link module:ol/xml~makeChildAppender} or
+ * {@link module:ol/xml~makeSimpleNodeFactory}, the top `objectStack` item needs
+ * to have this structure.
+ * @typedef {Object} NodeStackItem
+ * @property {Node} node
+ */
+
+/**
+ * @typedef {function(Element, Array<*>): void} Parser
+ */
+
+/**
+ * @typedef {function(Element, *, Array<*>): void} Serializer
+ */
+
+/**
+ * @type {string}
+ */
+export const XML_SCHEMA_INSTANCE_URI =
+ 'http://www.w3.org/2001/XMLSchema-instance';
+
+/**
+ * @param {string} namespaceURI Namespace URI.
+ * @param {string} qualifiedName Qualified name.
+ * @return {Element} Node.
+ */
+export function createElementNS(namespaceURI, qualifiedName) {
+ return getDocument().createElementNS(namespaceURI, qualifiedName);
+}
+
+/**
+ * Recursively grab all text content of child nodes into a single string.
+ * @param {Node} node Node.
+ * @param {boolean} normalizeWhitespace Normalize whitespace: remove all line
+ * breaks.
+ * @return {string} All text content.
+ * @api
+ */
+export function getAllTextContent(node, normalizeWhitespace) {
+ return getAllTextContent_(node, normalizeWhitespace, []).join('');
+}
+
+/**
+ * Recursively grab all text content of child nodes into a single string.
+ * @param {Node} node Node.
+ * @param {boolean} normalizeWhitespace Normalize whitespace: remove all line
+ * breaks.
+ * @param {Array} accumulator Accumulator.
+ * @private
+ * @return {Array} Accumulator.
+ */
+export function getAllTextContent_(node, normalizeWhitespace, accumulator) {
+ if (
+ node.nodeType == Node.CDATA_SECTION_NODE ||
+ node.nodeType == Node.TEXT_NODE
+ ) {
+ if (normalizeWhitespace) {
+ accumulator.push(String(node.nodeValue).replace(/(\r\n|\r|\n)/g, ''));
+ } else {
+ accumulator.push(node.nodeValue);
+ }
+ } else {
+ let n;
+ for (n = node.firstChild; n; n = n.nextSibling) {
+ getAllTextContent_(n, normalizeWhitespace, accumulator);
+ }
+ }
+ return accumulator;
+}
+
+/**
+ * @param {Object} object Object.
+ * @return {boolean} Is a document.
+ */
+export function isDocument(object) {
+ return 'documentElement' in object;
+}
+
+/**
+ * @param {Element} node Node.
+ * @param {?string} namespaceURI Namespace URI.
+ * @param {string} name Attribute name.
+ * @return {string} Value
+ */
+export function getAttributeNS(node, namespaceURI, name) {
+ return node.getAttributeNS(namespaceURI, name) || '';
+}
+
+/**
+ * Parse an XML string to an XML Document.
+ * @param {string} xml XML.
+ * @return {Document} Document.
+ * @api
+ */
+export function parse(xml) {
+ return new DOMParser().parseFromString(xml, 'application/xml');
+}
+
+/**
+ * Make an array extender function for extending the array at the top of the
+ * object stack.
+ * @param {function(this: T, Node, Array<*>): (Array<*>|undefined)} valueReader Value reader.
+ * @param {T=} opt_this The object to use as `this` in `valueReader`.
+ * @return {Parser} Parser.
+ * @template T
+ */
+export function makeArrayExtender(valueReader, opt_this) {
+ return (
+ /**
+ * @param {Node} node Node.
+ * @param {Array<*>} objectStack Object stack.
+ */
+ function (node, objectStack) {
+ const value = valueReader.call(
+ opt_this !== undefined ? opt_this : this,
+ node,
+ objectStack
+ );
+ if (value !== undefined) {
+ const array = /** @type {Array<*>} */ (objectStack[
+ objectStack.length - 1
+ ]);
+ extend(array, value);
+ }
+ }
+ );
+}
+
+/**
+ * Make an array pusher function for pushing to the array at the top of the
+ * object stack.
+ * @param {function(this: T, Element, Array<*>): *} valueReader Value reader.
+ * @param {T=} opt_this The object to use as `this` in `valueReader`.
+ * @return {Parser} Parser.
+ * @template T
+ */
+export function makeArrayPusher(valueReader, opt_this) {
+ return (
+ /**
+ * @param {Element} node Node.
+ * @param {Array<*>} objectStack Object stack.
+ */
+ function (node, objectStack) {
+ const value = valueReader.call(
+ opt_this !== undefined ? opt_this : this,
+ node,
+ objectStack
+ );
+ if (value !== undefined) {
+ const array = /** @type {Array<*>} */ (objectStack[
+ objectStack.length - 1
+ ]);
+ array.push(value);
+ }
+ }
+ );
+}
+
+/**
+ * Make an object stack replacer function for replacing the object at the
+ * top of the stack.
+ * @param {function(this: T, Node, Array<*>): *} valueReader Value reader.
+ * @param {T=} opt_this The object to use as `this` in `valueReader`.
+ * @return {Parser} Parser.
+ * @template T
+ */
+export function makeReplacer(valueReader, opt_this) {
+ return (
+ /**
+ * @param {Node} node Node.
+ * @param {Array<*>} objectStack Object stack.
+ */
+ function (node, objectStack) {
+ const value = valueReader.call(
+ opt_this !== undefined ? opt_this : this,
+ node,
+ objectStack
+ );
+ if (value !== undefined) {
+ objectStack[objectStack.length - 1] = value;
+ }
+ }
+ );
+}
+
+/**
+ * Make an object property pusher function for adding a property to the
+ * object at the top of the stack.
+ * @param {function(this: T, Element, Array<*>): *} valueReader Value reader.
+ * @param {string=} opt_property Property.
+ * @param {T=} opt_this The object to use as `this` in `valueReader`.
+ * @return {Parser} Parser.
+ * @template T
+ */
+export function makeObjectPropertyPusher(valueReader, opt_property, opt_this) {
+ return (
+ /**
+ * @param {Element} node Node.
+ * @param {Array<*>} objectStack Object stack.
+ */
+ function (node, objectStack) {
+ const value = valueReader.call(
+ opt_this !== undefined ? opt_this : this,
+ node,
+ objectStack
+ );
+ if (value !== undefined) {
+ const object = /** @type {!Object} */ (objectStack[
+ objectStack.length - 1
+ ]);
+ const property =
+ opt_property !== undefined ? opt_property : node.localName;
+ let array;
+ if (property in object) {
+ array = object[property];
+ } else {
+ array = [];
+ object[property] = array;
+ }
+ array.push(value);
+ }
+ }
+ );
+}
+
+/**
+ * Make an object property setter function.
+ * @param {function(this: T, Element, Array<*>): *} valueReader Value reader.
+ * @param {string=} opt_property Property.
+ * @param {T=} opt_this The object to use as `this` in `valueReader`.
+ * @return {Parser} Parser.
+ * @template T
+ */
+export function makeObjectPropertySetter(valueReader, opt_property, opt_this) {
+ return (
+ /**
+ * @param {Element} node Node.
+ * @param {Array<*>} objectStack Object stack.
+ */
+ function (node, objectStack) {
+ const value = valueReader.call(
+ opt_this !== undefined ? opt_this : this,
+ node,
+ objectStack
+ );
+ if (value !== undefined) {
+ const object = /** @type {!Object} */ (objectStack[
+ objectStack.length - 1
+ ]);
+ const property =
+ opt_property !== undefined ? opt_property : node.localName;
+ object[property] = value;
+ }
+ }
+ );
+}
+
+/**
+ * Create a serializer that appends nodes written by its `nodeWriter` to its
+ * designated parent. The parent is the `node` of the
+ * {@link module:ol/xml~NodeStackItem} at the top of the `objectStack`.
+ * @param {function(this: T, Node, V, Array<*>): void} nodeWriter Node writer.
+ * @param {T=} opt_this The object to use as `this` in `nodeWriter`.
+ * @return {Serializer} Serializer.
+ * @template T, V
+ */
+export function makeChildAppender(nodeWriter, opt_this) {
+ return function (node, value, objectStack) {
+ nodeWriter.call(
+ opt_this !== undefined ? opt_this : this,
+ node,
+ value,
+ objectStack
+ );
+ const parent = /** @type {NodeStackItem} */ (objectStack[
+ objectStack.length - 1
+ ]);
+ const parentNode = parent.node;
+ parentNode.appendChild(node);
+ };
+}
+
+/**
+ * Create a serializer that calls the provided `nodeWriter` from
+ * {@link module:ol/xml~serialize}. This can be used by the parent writer to have the
+ * 'nodeWriter' called with an array of values when the `nodeWriter` was
+ * designed to serialize a single item. An example would be a LineString
+ * geometry writer, which could be reused for writing MultiLineString
+ * geometries.
+ * @param {function(this: T, Element, V, Array<*>): void} nodeWriter Node writer.
+ * @param {T=} opt_this The object to use as `this` in `nodeWriter`.
+ * @return {Serializer} Serializer.
+ * @template T, V
+ */
+export function makeArraySerializer(nodeWriter, opt_this) {
+ let serializersNS, nodeFactory;
+ return function (node, value, objectStack) {
+ if (serializersNS === undefined) {
+ serializersNS = {};
+ const serializers = {};
+ serializers[node.localName] = nodeWriter;
+ serializersNS[node.namespaceURI] = serializers;
+ nodeFactory = makeSimpleNodeFactory(node.localName);
+ }
+ serialize(serializersNS, nodeFactory, value, objectStack);
+ };
+}
+
+/**
+ * Create a node factory which can use the `opt_keys` passed to
+ * {@link module:ol/xml~serialize} or {@link module:ol/xml~pushSerializeAndPop} as node names,
+ * or a fixed node name. The namespace of the created nodes can either be fixed,
+ * or the parent namespace will be used.
+ * @param {string=} opt_nodeName Fixed node name which will be used for all
+ * created nodes. If not provided, the 3rd argument to the resulting node
+ * factory needs to be provided and will be the nodeName.
+ * @param {string=} opt_namespaceURI Fixed namespace URI which will be used for
+ * all created nodes. If not provided, the namespace of the parent node will
+ * be used.
+ * @return {function(*, Array<*>, string=): (Node|undefined)} Node factory.
+ */
+export function makeSimpleNodeFactory(opt_nodeName, opt_namespaceURI) {
+ const fixedNodeName = opt_nodeName;
+ return (
+ /**
+ * @param {*} value Value.
+ * @param {Array<*>} objectStack Object stack.
+ * @param {string=} opt_nodeName Node name.
+ * @return {Node} Node.
+ */
+ function (value, objectStack, opt_nodeName) {
+ const context = /** @type {NodeStackItem} */ (objectStack[
+ objectStack.length - 1
+ ]);
+ const node = context.node;
+ let nodeName = fixedNodeName;
+ if (nodeName === undefined) {
+ nodeName = opt_nodeName;
+ }
+
+ const namespaceURI =
+ opt_namespaceURI !== undefined ? opt_namespaceURI : node.namespaceURI;
+ return createElementNS(namespaceURI, /** @type {string} */ (nodeName));
+ }
+ );
+}
+
+/**
+ * A node factory that creates a node using the parent's `namespaceURI` and the
+ * `nodeName` passed by {@link module:ol/xml~serialize} or
+ * {@link module:ol/xml~pushSerializeAndPop} to the node factory.
+ * @const
+ * @type {function(*, Array<*>, string=): (Node|undefined)}
+ */
+export const OBJECT_PROPERTY_NODE_FACTORY = makeSimpleNodeFactory();
+
+/**
+ * Create an array of `values` to be used with {@link module:ol/xml~serialize} or
+ * {@link module:ol/xml~pushSerializeAndPop}, where `orderedKeys` has to be provided as
+ * `opt_key` argument.
+ * @param {Object} object Key-value pairs for the sequence. Keys can
+ * be a subset of the `orderedKeys`.
+ * @param {Array} orderedKeys Keys in the order of the sequence.
+ * @return {Array<*>} Values in the order of the sequence. The resulting array
+ * has the same length as the `orderedKeys` array. Values that are not
+ * present in `object` will be `undefined` in the resulting array.
+ */
+export function makeSequence(object, orderedKeys) {
+ const length = orderedKeys.length;
+ const sequence = new Array(length);
+ for (let i = 0; i < length; ++i) {
+ sequence[i] = object[orderedKeys[i]];
+ }
+ return sequence;
+}
+
+/**
+ * Create a namespaced structure, using the same values for each namespace.
+ * This can be used as a starting point for versioned parsers, when only a few
+ * values are version specific.
+ * @param {Array} namespaceURIs Namespace URIs.
+ * @param {T} structure Structure.
+ * @param {Object=} opt_structureNS Namespaced structure to add to.
+ * @return {Object} Namespaced structure.
+ * @template T
+ */
+export function makeStructureNS(namespaceURIs, structure, opt_structureNS) {
+ /**
+ * @type {Object}
+ */
+ const structureNS = opt_structureNS !== undefined ? opt_structureNS : {};
+ let i, ii;
+ for (i = 0, ii = namespaceURIs.length; i < ii; ++i) {
+ structureNS[namespaceURIs[i]] = structure;
+ }
+ return structureNS;
+}
+
+/**
+ * Parse a node using the parsers and object stack.
+ * @param {Object>} parsersNS
+ * Parsers by namespace.
+ * @param {Element} node Node.
+ * @param {Array<*>} objectStack Object stack.
+ * @param {*=} opt_this The object to use as `this`.
+ */
+export function parseNode(parsersNS, node, objectStack, opt_this) {
+ let n;
+ for (n = node.firstElementChild; n; n = n.nextElementSibling) {
+ const parsers = parsersNS[n.namespaceURI];
+ if (parsers !== undefined) {
+ const parser = parsers[n.localName];
+ if (parser !== undefined) {
+ parser.call(opt_this, n, objectStack);
+ }
+ }
+ }
+}
+
+/**
+ * Push an object on top of the stack, parse and return the popped object.
+ * @param {T} object Object.
+ * @param {Object>} parsersNS
+ * Parsers by namespace.
+ * @param {Element} node Node.
+ * @param {Array<*>} objectStack Object stack.
+ * @param {*=} opt_this The object to use as `this`.
+ * @return {T} Object.
+ * @template T
+ */
+export function pushParseAndPop(
+ object,
+ parsersNS,
+ node,
+ objectStack,
+ opt_this
+) {
+ objectStack.push(object);
+ parseNode(parsersNS, node, objectStack, opt_this);
+ return /** @type {T} */ (objectStack.pop());
+}
+
+/**
+ * Walk through an array of `values` and call a serializer for each value.
+ * @param {Object>} serializersNS
+ * Namespaced serializers.
+ * @param {function(this: T, *, Array<*>, (string|undefined)): (Node|undefined)} nodeFactory
+ * Node factory. The `nodeFactory` creates the node whose namespace and name
+ * will be used to choose a node writer from `serializersNS`. This
+ * separation allows us to decide what kind of node to create, depending on
+ * the value we want to serialize. An example for this would be different
+ * geometry writers based on the geometry type.
+ * @param {Array<*>} values Values to serialize. An example would be an array
+ * of {@link module:ol/Feature~Feature} instances.
+ * @param {Array<*>} objectStack Node stack.
+ * @param {Array=} opt_keys Keys of the `values`. Will be passed to the
+ * `nodeFactory`. This is used for serializing object literals where the
+ * node name relates to the property key. The array length of `opt_keys` has
+ * to match the length of `values`. For serializing a sequence, `opt_keys`
+ * determines the order of the sequence.
+ * @param {T=} opt_this The object to use as `this` for the node factory and
+ * serializers.
+ * @template T
+ */
+export function serialize(
+ serializersNS,
+ nodeFactory,
+ values,
+ objectStack,
+ opt_keys,
+ opt_this
+) {
+ const length = (opt_keys !== undefined ? opt_keys : values).length;
+ let value, node;
+ for (let i = 0; i < length; ++i) {
+ value = values[i];
+ if (value !== undefined) {
+ node = nodeFactory.call(
+ opt_this !== undefined ? opt_this : this,
+ value,
+ objectStack,
+ opt_keys !== undefined ? opt_keys[i] : undefined
+ );
+ if (node !== undefined) {
+ serializersNS[node.namespaceURI][node.localName].call(
+ opt_this,
+ node,
+ value,
+ objectStack
+ );
+ }
+ }
+ }
+}
+
+/**
+ * @param {O} object Object.
+ * @param {Object>} serializersNS
+ * Namespaced serializers.
+ * @param {function(this: T, *, Array<*>, (string|undefined)): (Node|undefined)} nodeFactory
+ * Node factory. The `nodeFactory` creates the node whose namespace and name
+ * will be used to choose a node writer from `serializersNS`. This
+ * separation allows us to decide what kind of node to create, depending on
+ * the value we want to serialize. An example for this would be different
+ * geometry writers based on the geometry type.
+ * @param {Array<*>} values Values to serialize. An example would be an array
+ * of {@link module:ol/Feature~Feature} instances.
+ * @param {Array<*>} objectStack Node stack.
+ * @param {Array=} opt_keys Keys of the `values`. Will be passed to the
+ * `nodeFactory`. This is used for serializing object literals where the
+ * node name relates to the property key. The array length of `opt_keys` has
+ * to match the length of `values`. For serializing a sequence, `opt_keys`
+ * determines the order of the sequence.
+ * @param {T=} opt_this The object to use as `this` for the node factory and
+ * serializers.
+ * @return {O|undefined} Object.
+ * @template O, T
+ */
+export function pushSerializeAndPop(
+ object,
+ serializersNS,
+ nodeFactory,
+ values,
+ objectStack,
+ opt_keys,
+ opt_this
+) {
+ objectStack.push(object);
+ serialize(
+ serializersNS,
+ nodeFactory,
+ values,
+ objectStack,
+ opt_keys,
+ opt_this
+ );
+ return /** @type {O|undefined} */ (objectStack.pop());
+}
+
+let xmlSerializer_ = undefined;
+
+/**
+ * Register a XMLSerializer. Can be used to inject a XMLSerializer
+ * where there is no globally available implementation.
+ *
+ * @param {XMLSerializer} xmlSerializer A XMLSerializer.
+ * @api
+ */
+export function registerXMLSerializer(xmlSerializer) {
+ xmlSerializer_ = xmlSerializer;
+}
+
+/**
+ * @return {XMLSerializer} The XMLSerializer.
+ */
+export function getXMLSerializer() {
+ if (xmlSerializer_ === undefined && typeof XMLSerializer !== 'undefined') {
+ xmlSerializer_ = new XMLSerializer();
+ }
+ return xmlSerializer_;
+}
+
+let document_ = undefined;
+
+/**
+ * Register a Document to use when creating nodes for XML serializations. Can be used
+ * to inject a Document where there is no globally available implementation.
+ *
+ * @param {Document} document A Document.
+ * @api
+ */
+export function registerDocument(document) {
+ document_ = document;
+}
+
+/**
+ * Get a document that should be used when creating nodes for XML serializations.
+ * @return {Document} The document.
+ */
+export function getDocument() {
+ if (document_ === undefined && typeof document !== 'undefined') {
+ document_ = document.implementation.createDocument('', '', null);
+ }
+ return document_;
+}
\ No newline at end of file
diff --git a/src/common/overlay/Bar.js b/src/common/overlay/Bar.js
new file mode 100644
index 000000000..5443d1833
--- /dev/null
+++ b/src/common/overlay/Bar.js
@@ -0,0 +1,350 @@
+import {Zondy} from '../../service/common/Base';
+import {copyAttributesWithClip} from '../../service/common/Util';
+import {ShapeFactory} from './feature/ShapeFactory';
+import {Polygon as FeaturePolygon} from './feature/Polygon';
+import {Color} from './levelRender/Color';
+import {Graph} from './Graph';
+
+/**
+ * @private
+ * @class Zondy.Theme.Bar
+ * @classdesc 柱状图 。
+ * @example
+ * // barStyleByCodomain参数用法如下:
+ * // barStyleByCodomain 的每个元素是个包含值域信息和与值域对应样式信息的对象,该对象(必须)有三个属性:
+ * // start: 值域值下限(包含);
+ * // end: 值域值上限(不包含);
+ * // style: 数据可视化图形的 style,这个样式对象的可设属性: 。
+ * // barStyleByCodomain 数组形如:
+ * [
+ * {
+ * start:0,
+ * end:250,
+ * style:{
+ * fillColor:"#00CD00"
+ * }
+ * },
+ * {
+ * start:250,
+ * end:500,
+ * style:{
+ * fillColor:"#00EE00"
+ * }
+ * },
+ * {
+ * start:500,
+ * end:750,
+ * style:{
+ * fillColor:"#00FF7F"
+ * }
+ * },
+ * {
+ * start:750,
+ * end:1500,
+ * style:{
+ * fillColor:"#00FF00"
+ * }
+ * }
+ * ]
+ * @extends Zondy.Feature.Theme.Graph
+ * @param {Zondy.Feature.Vector} data - 用户数据。
+ * @param {Zondy.Layer.Graph} layer - 此专题要素所在图层。
+ * @param {Array.} fields - data 属性中的参与此图表生成的属性字段名称。
+ * @param {Zondy.Theme.Bar.setting} setting - 图表配置对象。
+ * @param {Zondy.LonLat} [lonlat] - 专题要素地理位置。默认为 data 指代的地理要素 Bounds 中心。
+ *
+ * @typedef {Object} Zondy.Theme.Bar.setting
+ * @property {number} width - 专题要素(图表)宽度。
+ * @property {number} height - 专题要素(图表)高度。
+ * @property {Array.} codomain - 图表允许展示的数据值域,长度为 2 的一维数组,第一个元素表示值域下限,第二个元素表示值域上限。
+ * @property {number} [XOffset] - 专题要素(图表)在 X 方向上的偏移值,单位像素。
+ * @property {number} [YOffset] - 专题要素(图表)在 Y 方向上的偏移值,单位像素。
+ * @property {Array.} [dataViewBoxParameter] - 数据视图框 dataViewBox 参数,它是指图表框 chartBox (由图表位置、图表宽度、图表高度构成的图表范围框)
+ * 在左、下,右,上四个方向上的内偏距值。当使用坐标轴时 dataViewBoxParameter 的默认值为:[45, 15, 15, 15];
+ * 不使用坐标轴时 dataViewBoxParameter 的默认值为:[5, 5, 5, 5]。
+ * @property {number} [decimalNumber] - 数据值数组 dataValues 元素值小数位数,数据的小数位处理参数,取值范围:[0, 16]。如果不设置此参数,在取数据值时不对数据做小数位处理。
+ * @property {boolean} [useBackground=true] - 是否使用图表背景框。
+ * @property {Zondy.Feature.ShapeParameters.Rectangle.style} [backgroundStyle] - 背景样式。
+ * @property {Array.} [backgroundRadius=[0, 0, 0, 0]] - 背景框矩形圆角半径,可以用数组分别指定四个角的圆角半径,设:左上、右上、右下、左下角的半径依次为 r1、r2、r3、r4 ,
+ * 则 backgroundRadius 为 [r1、r2、r3、r4]。
+ * @property {Array.} xShapeBlank - 水平方向上的图形空白间隔参数。长度为 3 的数组,第一元素表示第一个图形左端与数据视图框左端的空白间距,第二个元素表示图形间空白间距,
+ * 第三个元素表示最后一个图形右端与数据视图框右端端的空白间距 。
+ * @property {boolean} [showShadow=true] - 阴影开关。
+ * @property {Object} [barShadowStyle] - 阴影样式,如:{shadowBlur : 8, shadowOffsetX: 2 , shadowOffsetY : 2,shadowColor : "rgba(100,100,100,0.8)"}
+ * @property {Array.} [barLinearGradient] - 按字段设置柱条样式[渐变开始颜色,渐变终止颜色] 与 themeLayer.themeFields 中的字段一一对应),
+ * 如:[["#00FF00","#00CD00"],["#00CCFF","#5E87A2"],["#00FF66","#669985"],["#CCFF00","#94A25E"],["#FF9900","#A2945E"]]
+ * @property {boolean} [useAxis=true] - 是否使用坐标轴。
+ * @property {Zondy.Feature.ShapeParameters.Line.style} [axisStyle] - 坐标轴样式。
+ * @property {boolean} [axisUseArrow=false] - 坐标轴是否使用箭头。
+ * @property {number} [axisYTick=0] - y 轴刻度数量。
+ * @property {Array.} [axisYLabels] - y 轴上的标签组内容,标签顺序沿着数据视图框左面条边自上而下,等距排布。例如:["1000", "750", "500", "250", "0"]。
+ * @property {Zondy.Feature.ShapeParameters.Label.style} [axisYLabelsStyle] - y 轴上的标签组样式。
+ * @property {Array.} [axisYLabelsOffset=[0,0]] - y 轴上的标签组偏移量。长度为 2 的数组,数组第一项表示 y 轴标签组横向上的偏移量,向左为正;数组第二项表示 y 轴标签组纵向上的偏移量,向下为正。
+ * @property {Array.} [axisXLabels] - x 轴上的标签组内容,标签顺序沿着数据视图框下面条边自左向右排布,例如:["92年", "95年", "99年"]。标签排布规则:当标签数量与 xShapeInfo 中的属性 xShapeCenter 数量相同(即标签个数与数据个数相等时), 按照 xShapeCenter 提供的位置排布标签,否则沿数据视图框下面条边等距排布标签。
+ * @property {Zondy.Feature.ShapeParameters.Label.style} [axisXLabelsStyle] - x 轴上的标签组样式。
+ * @property {Array.} [axisXLabelsOffset=[0,0]] - x 轴上的标签组偏移量。长度为 2 的数组,数组第一项表示 x 轴标签组横向上的偏移量,向左为正;数组第二项表示 x 轴标签组纵向上的偏移量,向下为正。
+ * @property {boolean} [useXReferenceLine] - 是否使用水平参考线,如果为 true,在 axisYTick 大于 0 时有效,水平参考线是 y 轴刻度在数据视图框里的延伸。
+ * @property {Zondy.Feature.ShapeParameters.Line.style} xReferenceLineStyle - 水平参考线样式。
+ * @property {Object} barStyle - 柱状图柱条基础 style,此参数控制柱条基础样式,优先级低于 barStyleByFields 和 barStyleByCodomain。
+ * @property {Array.} barStyleByFields - 按专题字段 themeFields()为柱条赋 style,此参数按字段控制柱条样式,优先级低于 barStyleByCodomain,高于 barStyle。此数组中的元素是样式对象。此参数中的 style 与 themeFields 中的字段一一对应 。例如: themeFields() 为 ["POP_1992", "POP_1995", "POP_1999"],barStyleByFields 为[style1, style2, style3],则在图表中,字段 POP_1992 对应的柱条使用 style1,字段 POP_1995 对应的柱条使用 style2 ,字段 POP_1999 对应的柱条使用 style3。
+ * @property {Array.} barStyleByCodomain - 按柱条代表的数据值所在值域范围控制柱条样式,优先级高于 barStyle 和 barStyleByFields。
+ * @property {Object} [barHoverStyle] - 柱条 hover 状态时的样式,barHoverAble 为 true 时有效。
+ * @property {Object} [barHoverAble] - 是否允许柱条使用 hover 状态,默认允许。同时设置 barHoverAble 和 barClickAble 为 false,可以直接屏蔽柱条对专题图层事件的响应。
+ * @property {Object} [barClickAble] - 是否允许柱条被点击,默认允许。同时设置 barHoverAble 和 barClickAble 为 false,可以直接屏蔽柱条对专题图层事件的响应。
+ */
+class Bar extends Graph {
+
+ constructor(data, layer, fields, setting, lonlat, option) {
+ super(data, layer, fields, setting, lonlat, option);
+ this.CLASS_NAME = "Zondy.Theme.Bar";
+ }
+
+ /**
+ * @function Zondy.Theme.Bar.prototype.destroy
+ * @override
+ */
+ destroy() {
+ super.destroy();
+ }
+
+ /**
+ * @function Zondy.Theme.Bar.prototype.assembleShapes
+ * @description 图表图形装配函数。
+ */
+ assembleShapes() {
+ //默认渐变颜色数组
+ var deafaultColors = [["#00FF00", "#00CD00"], ["#00CCFF", "#5E87A2"], ["#00FF66", "#669985"], ["#CCFF00", "#94A25E"], ["#FF9900", "#A2945E"]];
+
+ //默认阴影
+ var deafaultShawdow = {
+ showShadow: true,
+ shadowBlur: 8,
+ shadowColor: "rgba(100,100,100,0.8)",
+ shadowOffsetX: 2,
+ shadowOffsetY: 2
+ };
+
+ // 图表配置对象
+ var sets = this.setting;
+
+ if (!sets.barLinearGradient) {
+ sets.barLinearGradient = deafaultColors;
+ }
+
+ // 默认数据视图框
+ if (!sets.dataViewBoxParameter) {
+ if (typeof (sets.useAxis) === "undefined" || sets.useAxis) {
+ sets.dataViewBoxParameter = [45, 15, 15, 15];
+ } else {
+ sets.dataViewBoxParameter = [5, 5, 5, 5];
+ }
+ }
+
+ // 重要步骤:初始化参数
+ if (!this.initBaseParameter()) {
+ return;
+ }
+ // 值域
+ var codomain = this.DVBCodomain;
+ // 重要步骤:定义图表 Bar 数据视图框中单位值的含义
+ this.DVBUnitValue = (codomain[1] - codomain[0]) / this.DVBHeight;
+
+ // 数据视图域
+ var dvb = this.dataViewBox;
+ // 用户数据值
+ var fv = this.dataValues;
+ if (fv.length < 1) {
+ return;
+ } // 没有数据
+
+ // 数据溢出值域范围处理
+ for (let i = 0, fvLen = fv.length; i < fvLen; i++) {
+ if (fv[i] < codomain[0] || fv[i] > codomain[1]) {
+ return;
+ }
+ }
+
+ // 获取 x 轴上的图形信息
+ var xShapeInfo = this.calculateXShapeInfo();
+ if (!xShapeInfo) {
+ return;
+ }
+ // 每个柱条 x 位置
+ var xsLoc = xShapeInfo.xPositions;
+ // 柱条宽度
+ var xsWdith = xShapeInfo.width;
+
+ // 背景框,默认启用
+ if (typeof (sets.useBackground) === "undefined" || sets.useBackground) {
+ // 将背景框图形添加到模型的 shapes 数组,注意添加顺序,后添加的图形在先添加的图形之上。
+ this.shapes.push(ShapeFactory.Background(this.shapeFactory, this.chartBox, sets));
+ }
+
+ // 坐标轴, 默认启用
+ if (typeof (sets.useAxis) === "undefined" || sets.useAxis) {
+ // 添加坐标轴图形数组
+ this.shapes = this.shapes.concat(ShapeFactory.GraphAxis(this.shapeFactory, dvb, sets, xShapeInfo));
+ }
+
+ for (var i = 0; i < fv.length; i++) {
+ // 计算柱条 top 边的 y 轴坐标值
+ var yPx = dvb[1] - (fv[i] - codomain[0]) / this.DVBUnitValue;
+
+ // 柱条节点数组
+ var poiLists = [
+ [xsLoc[i] - xsWdith / 2, dvb[1] - 1],
+ [xsLoc[i] + xsWdith / 2, dvb[1] - 1],
+ [xsLoc[i] + xsWdith / 2, yPx],
+ [xsLoc[i] - xsWdith / 2, yPx]
+ ];
+
+ // 柱条参数对象(一个面参数对象)
+ var barParams = new FeaturePolygon(poiLists);
+
+ // 柱条 阴影 style
+ if (typeof (sets.showShadow) === "undefined" || sets.showShadow) {
+ if (sets.barShadowStyle) {
+ var sss = sets.barShadowStyle;
+ if (sss.shadowBlur) {
+ deafaultShawdow.shadowBlur = sss.shadowBlur;
+ }
+ if (sss.shadowColor) {
+ deafaultShawdow.shadowColor = sss.shadowColor;
+ }
+ if (sss.shadowOffsetX) {
+ deafaultShawdow.shadowOffsetX = sss.shadowOffsetX;
+ }
+ if (sss.shadowOffsetY) {
+ deafaultShawdow.shadowOffsetY = sss.shadowOffsetY;
+ }
+ }
+ barParams.style = {};
+ copyAttributesWithClip(barParams.style, deafaultShawdow);
+ }
+
+ // 图形携带的数据信息
+ barParams.refDataID = this.data.FID;
+ barParams.dataInfo = {
+ field: this.fields[i],
+ value: fv[i]
+ };
+
+ // 柱条 hover click
+ if (typeof (sets.barHoverAble) !== "undefined") {
+ barParams.hoverable = sets.barHoverAble;
+ }
+ if (typeof (sets.barClickAble) !== "undefined") {
+ barParams.clickable = sets.barClickAble;
+ }
+
+ // 创建柱条并添加到图表图形数组中
+ this.shapes.push(this.shapeFactory.createShape(barParams));
+ }
+
+ // 重要步骤:将图形转为由相对坐标表示的图形,以便在地图平移缩放过程中快速重绘图形
+ // (统计专题图模块从结构上要求使用相对坐标,assembleShapes() 函数必须在图形装配完成后调用 shapesConvertToRelativeCoordinate() 函数)
+ this.shapesConvertToRelativeCoordinate();
+ }
+
+ /**
+ * @function Zondy.Theme.Bar.prototype.calculateXShapeInfo
+ * @description 计算 X 轴方向上的图形信息,此信息是一个对象,包含两个属性,
+ * 属性 xPositions 是一个一维数组,该数组元素表示图形在 x 轴方向上的像素坐标值,
+ * 如果图形在 x 方向上有一定宽度,通常取图形在 x 方向上的中心点为图形在 x 方向上的坐标值。
+ * width 表示图形的宽度(特别注意:点的宽度始终为 0,而不是其直径)。
+ * 本函数中图形配置对象 setting 可设属性:
+ * xShapeBlank - {Array.} 水平方向上的图形空白间隔参数。
+ * 长度为 3 的数组,第一元素表示第一个图形左端与数据视图框左端的空白间距,第二个元素表示图形间空白间距,
+ * 第三个元素表示最后一个图形右端与数据视图框右端端的空白间距 。
+ * @returns {Object} 如果计算失败,返回 null;如果计算成功,返回 X 轴方向上的图形信息,此信息是一个对象,包含以下两个属性:
+ * xPositions - {Array.} 表示图形在 x 轴方向上的像素坐标值,如果图形在 x 方向上有一定宽度,通常取图形在 x 方向上的中心点为图形在 x 方向上的坐标值。
+ * width - {number} 表示图形的宽度(特别注意:点的宽度始终为 0,而不是其直径)。
+ *
+ */
+ calculateXShapeInfo() {
+ var dvb = this.dataViewBox; // 数据视图框
+ var sets = this.setting; // 图表配置对象
+ var fvc = this.dataValues.length; // 数组值个数
+
+ if (fvc < 1) {
+ return null;
+ }
+
+ var xBlank; // x 轴空白间隔参数
+ var xShapePositions = []; // x 轴上图形的位置
+ var xShapeWidth = 0; // x 轴上图形宽度(自适应)
+ var dvbWidth = this.DVBWidth; // 数据视图框宽度
+
+ // x 轴空白间隔参数处理
+ if (sets.xShapeBlank && sets.xShapeBlank.length && sets.xShapeBlank.length === 3) {
+ xBlank = sets.xShapeBlank;
+ var xsLen = dvbWidth - (xBlank[0] + xBlank[2] + (fvc - 1) * xBlank[1]);
+ if (xsLen <= fvc) {
+ return null;
+ }
+ xShapeWidth = xsLen / fvc
+ } else {
+ // 默认使用等距离空白间隔,空白间隔为图形宽度
+ xShapeWidth = dvbWidth / (2 * fvc + 1);
+ xBlank = [xShapeWidth, xShapeWidth, xShapeWidth];
+ }
+
+ // 图形 x 轴上的位置计算
+ var xOffset = 0;
+ for (var i = 0; i < fvc; i++) {
+ if (i === 0) {
+ xOffset = xBlank[0] + xShapeWidth / 2;
+ } else {
+ xOffset += (xShapeWidth + xBlank[1]);
+ }
+
+ xShapePositions.push(dvb[0] + xOffset);
+ }
+
+ return {
+ "xPositions": xShapePositions,
+ "width": xShapeWidth
+ };
+ }
+
+ /**
+ * @function Zondy.Theme.Bar.prototype.resetLinearGradient
+ * @description 图表的相对坐标存在的时候,重新计算渐变的颜色(目前用于二维柱状图 所以子类实现此方法)。
+ */
+ resetLinearGradient() {
+ if (this.RelativeCoordinate) {
+ var shpelength = this.shapes.length;
+ var barLinearGradient = this.setting.barLinearGradient;
+ var index = -1;
+ for (var i = 0; i < shpelength; i++) {
+ var shape = this.shapes[i];
+ if (shape.CLASS_NAME === "Zondy.LevelRenderer.Shape.SmicPolygon") {
+ var style = shape.style;
+ //计算出当前的绝对 x y
+ var x1 = this.location[0] + style.pointList[0][0];
+ var x2 = this.location[0] + style.pointList[1][0];
+
+ //渐变颜色
+ index++;
+ //以防定义的颜色数组不够用
+ if (index >= barLinearGradient.length) {
+ index = index % barLinearGradient.length;
+ }
+ var color1 = barLinearGradient[index][0];
+ var color2 = barLinearGradient[index][1];
+
+ //颜色
+ var zcolor = new Color();
+ var linearGradient = zcolor.getLinearGradient(x1, 0, x2, 0,
+ [[0, color1], [1, color2]]);
+
+ //赋值
+ shape.style.color = linearGradient;
+ }
+ }
+ }
+ }
+}
+
+export {Bar};
+Zondy.Theme.Bar = Bar;
\ No newline at end of file
diff --git a/src/common/overlay/Bar3D.js b/src/common/overlay/Bar3D.js
new file mode 100644
index 000000000..f96493630
--- /dev/null
+++ b/src/common/overlay/Bar3D.js
@@ -0,0 +1,432 @@
+import {Zondy} from '../../service/common/Base';
+import {newGuid} from '../../service/common/Util';
+import {ShapeFactory} from './feature/ShapeFactory';
+import {Polygon as FeaturePolygon} from './feature/Polygon';
+import {Graph} from './Graph';
+
+/**
+ * @private
+ * @class Zondy.Theme.Bar3D
+ * @classdesc 三维柱状图 。
+ * @extends Zondy.Feature.Theme.Graph
+ * @param {Zondy.Feature.Vector} data - 用户数据。
+ * @param {Zondy.Layer.Graph} layer - 此专题要素所在图层。
+ * @param {Array.} fields - data 中的参与此图表生成的字段名称。
+ * @param {Zondy.Theme.Bar3D.setting} setting - 图表配置对象。
+ * @param {Zondy.LonLat} [lonlat] - 专题要素地理位置,默认为 data 指代的地理要素 Bounds 中心。
+ *
+ * @typedef {Object} Zondy.Theme.Bar3D.setting
+ * @property {number} width - 专题要素(图表)宽度。
+ * @property {number} height - 专题要素(图表)高度。
+ * @property {Array.} codomain - 图表允许展示的数据值域,长度为 2 的一维数组,第一个元素表示值域下限,第二个元素表示值域上限。
+ * @property {number} [XOffset] - 专题要素(图表)在 X 方向上的偏移值,单位像素。
+ * @property {number} [YOffset] - 专题要素(图表)在 Y 方向上的偏移值,单位像素。
+ * @property {Array.} [dataViewBoxParameter] - 数据视图框 dataViewBox 参数,它是指图表框 chartBox (由图表位置、图表宽度、图表高度构成的图表范围框)在左、下,右,上四个方向上的内偏距值。当使用坐标轴时 dataViewBoxParameter 的默认值为:[45, 25, 20, 20];不使用坐标轴时 dataViewBoxParameter 的默认值为:[5, 5, 5, 5]。
+ * @property {number} [decimalNumber] - 数据值数组 dataValues 元素值小数位数,数据的小数位处理参数,取值范围:[0, 16]。如果不设置此参数,在取数据值时不对数据做小数位处理。
+ * @property {boolean} [useBackground=true] - 是否使用图表背景框。
+ * @property {Zondy.Feature.ShapeParameters.Rectangle.style} [backgroundStyle] - 背景样式。
+ * @property {Array.} [backgroundRadius=[0, 0, 0, 0]] - 背景框矩形圆角半径,可以用数组分别指定四个角的圆角半径,设:左上、右上、右下、左下角的半径依次为 r1、r2、r3、r4 ,则 backgroundRadius 为 [r1、r2、r3、r4 ]。
+ * @property {Array.} [xShapeBlank] - 水平方向上的图形空白间隔参数。长度为 3 的数组,第一元素表示第一个图形左端与数据视图框左端的空白间距,第二个元素表示图形间空白间距,第三个元素表示最后一个图形右端与数据视图框右端端的空白间距 。
+ * @property {number} [bar3DParameter=10] - 3D 柱状参数,3d柱形正面相对于背面向 x 轴和 y 轴负方向偏移的绝对值。
+ * @property {boolean} [useAxis=true] - 是否使用坐标轴。
+ * @property {Zondy.Feature.ShapeParameters.Line.style} [axisStyle] - 坐标轴样式。
+ * @property {boolean} [axisUseArrow=true] -坐标轴是否使用箭头。
+ * @property {number} [axisYTick=0] - y 轴刻度数量。
+ * @property {Array.} [axisYLabels] - y 轴上的标签组内容,标签顺序沿着数据视图框左面条边自上而下,等距排布。例如:["1000", "750", "500", "250", "0"]。
+ * @property {Zondy.Feature.ShapeParameters.Label.style} [axisYLabelsStyle] - y 轴上的标签组样式。
+ * @property {Array.} [axisYLabelsOffset=0] - y 轴上的标签组偏移量。长度为 2 的数组,数组第一项表示 y 轴标签组横向上的偏移量,向左为正,默认值:0;数组第二项表示 y 轴标签组纵向上的偏移量,向下为正。
+ * @property {Array.} [axisXLabels] - x 轴上的标签组内容,标签顺序沿着数据视图框下面条边自左向右排布,例如:["92年", "95年", "99年"]。标签排布规则:当标签数量与 xShapeInfo 中的属性 xShapeCenter 数量相同(即标签个数与数据个数相等时), 按照 xShapeCenter 提供的位置排布标签,否则沿数据视图框下面条边等距排布标签。
+ * @property {Zondy.Feature.ShapeParameters.Label.style} axisXLabelsStyle - x 轴上的标签组样式。
+ * @property {Array.} axisXLabelsOffset - x 轴上的标签组偏移量。长度为 2 的数组,数组第一项表示 x 轴标签组横向上的偏移量,向左为正,默认值:-10;数组第二项表示 x 轴标签组纵向上的偏移量,向下为正,默认值:10。
+ * @property {boolean} [useXReferenceLine] - 是否使用水平参考线,如果为 true,在 axisYTick 大于 0 时有效,水平参考线是 y 轴刻度在数据视图框里的延伸。
+ * @property {Zondy.Feature.ShapeParameters.Line.style} [xReferenceLineStyle] - 水平参考线样式。
+ * @property {number} [axis3DParameter=20] - 3D 坐标轴参数,此属性值在大于等于 15 时有效。
+ * @property {Zondy.Feature.ShapeParameters.Polygon.style} barFaceStyle - 3d 柱状图柱条正面基础 style,此参数控制柱条正面基础样式,优先级低于 barFaceStyleByFields 和 barFaceStyleByCodomain。
+ * @property {Array.} [barFaceStyleByFields] - 按专题字段 themeFields()为柱条正面赋 style,此参数按字段控制柱条正面样式,优先级低于 barFaceStyleByCodomain,高于 barFaceStyle。此数组中的元素是样式对象。此参数中的 style 与 themeFields 中的字段一一对应 。例如: themeFields() 为 ["POP_1992", "POP_1995", "POP_1999"],barFaceStyleByFields 为[style1, style2, style3],则在图表中,字段 POP_1992 对应的柱条正面使用 style1,字段 POP_1995 对应的柱条正面使用 style2 ,字段 POP_1999 对应的柱条正面使用 style3。
+ * @property {Array.} [barFaceStyleByCodomain] - 按柱条正面代表的数据值所在值域范围控制柱条正面样式,优先级高于 barFaceStyle 和 barFaceStyleByFields。
+ * @property {Zondy.Feature.ShapeParameters.Polygon.style} [barSideStyle=barFaceStyle] - 3d 柱状图柱条侧面基础 style,此参数控制柱条侧面基础样式,优先级低于 barSideStyleByFields 和 barSideStyleByCodomain。
+ * @property {Array.} [barSideStyleByFields] - 按专题字段 themeFields()为柱条侧面赋style,此数按字段控制柱条侧面样式,优先级低于 barSideStyleByCodomain,高于 barSideStyle。此数组中的元素是样式对象。此参数中的 style 与 themeFields 中的字段一一对应 。例如: themeFields() 为 ["POP_1992", "POP_1995", "POP_1999"],barSideStyleByFields 为[style1, style2, style3],则在图表中,字段 POP_1992 对应的柱条侧面使用 style1,字段 POP_1995对应的柱条侧面使用style2,字段POP_1999对应的柱条侧面使用style3。默认值:barFaceStyleByFields。
+ * @property {Array.} [barSideStyleByCodomain=barFaceStyleByCodomain] - 按柱条侧面代表的数据值所在值域范围控制柱条侧面样式,优先级高于 barSideStyle 和 barSideStyleByFields。
+ * @property {Object} [barFaceHoverStyle] - 3d 柱条正面 hover 状态时的样式,barHoverAble 为 true 时有效。
+ * @property {Object} [barSideHoverStyle=barFaceHoverStyle] - 3d 柱条侧面 hover 状态时的样式,barHoverAble 为 true 时有效。
+ * @property {Object} [barTopHoverStyle=barFaceHoverStyle] - 3d 柱条顶面 hover 状态时的样式,barHoverAble 为 true 时有效。
+ * @property {boolean} [barHoverAble=true] - 是否允许柱条使用 hover 状态。同时设置 barHoverAble 和 barClickAble 为 false,可以直接屏蔽柱条对专题图层事件的响应。
+ * @property {boolean} [barClickAble=true] - 是否允许柱条被点击。同时设置 barHoverAble 和 barClickAble 为 false,可以直接屏蔽柱条对专题图层事件的响应。
+ * @property {Zondy.Feature.ShapeParameters.Polygon.style} [barTopStyle=barFaceStyle] - 3d 柱状图柱条顶面基础 style,此参数控制柱条顶面基础样式,优先级低于 barTopStyleByFields 和 barTopStyleByCodomain。
+ * @property {Array.} [barTopStyleByFields=barFaceStyleByFields] - 按专题字段 themeFields()为柱条顶面赋 style,此参数按字段控制柱条顶面样式,优先级低于 barTopStyleByCodomain,高于 barTopStyle。此数组中的元素是样式对象。此参数中的 style 与 themeFields 中的字段一一对应 。例如: themeFields() 为 ["POP_1992", "POP_1995", "POP_1999"],barTopStyleByFields 为[style1, style2, style3],则在图表中,字段 POP_1992 对应的柱条顶面使用 style1,字段 POP_1995 对应的柱条顶面使用 style2 ,字段 POP_1999 对应的柱条顶面使用 style3。
+ * @property {Array.} [barTopStyleByCodomain=barFaceStyleByCodomain] - 按柱条顶面代表的数据值所在值域范围控制柱条顶面样式,优先级高于 barTopStyle 和 barTopStyleByFields。
+ *
+ * @example
+ * // barFaceStyleByCodomain 用法示例如下:
+ * // barFaceStyleByCodomain 的每个元素是个包含值域信息和与值域对应样式信息的对象,该对象(必须)有三个属性:
+ * // start: 值域值下限(包含);
+ * // end: 值域值上限(不包含);
+ * // style: 数据可视化图形的 style,这个样式对象的可设属性: 。
+ * // barFaceStyleByCodomain 数组形如:
+ * [
+ * {
+ * start:0,
+ * end:250,
+ * style:{
+ * fillColor:"#00CD00"
+ * }
+ * },
+ * {
+ * start:250,
+ * end:500,
+ * style:{
+ * fillColor:"#00EE00"
+ * }
+ * },
+ * {
+ * start:500,
+ * end:750,
+ * style:{
+ * fillColor:"#00FF7F"
+ * }
+ * },
+ * {
+ * start:750,
+ * end:1500,
+ * style:{
+ * fillColor:"#00FF00"
+ * }
+ * }
+ * ]
+ *
+ * @example
+ * // barSideStyleByCodomain 用法示例如下:
+ * // barSideStyleByCodomain 的每个元素是个包含值域信息和与值域对应样式信息的对象,该对象(必须)有三个属性:
+ * // start: 值域值下限(包含);
+ * // end: 值域值上限(不包含);
+ * // style: 数据可视化图形的 style,这个样式对象的可设属性: 。
+ * // barSideStyleByCodomain 数组形如:
+ * [
+ * {
+ * start:0,
+ * end:250,
+ * style:{
+ * fillColor:"#00CD00"
+ * }
+ * },
+ * {
+ * start:250,
+ * end:500,
+ * style:{
+ * fillColor:"#00EE00"
+ * }
+ * },
+ * {
+ * start:500,
+ * end:750,
+ * style:{
+ * fillColor:"#00FF7F"
+ * }
+ * },
+ * {
+ * start:750,
+ * end:1500,
+ * style:{
+ * fillColor:"#00FF00"
+ * }
+ * }
+ * ]
+ *
+ * @example
+ * // barTopStyleByCodomain 用法示例如下:
+ * // barTopStyleByCodomain 的每个元素是个包含值域信息和与值域对应样式信息的对象,该对象(必须)有三个属性:
+ * // start: 值域值下限(包含);
+ * // end: 值域值上限(不包含);
+ * // style: 数据可视化图形的 style,这个样式对象的可设属性: 。
+ * // barTopStyleByCodomain 数组形如:
+ * [
+ * {
+ * start:0,
+ * end:250,
+ * style:{
+ * fillColor:"#00CD00"
+ * }
+ * },
+ * {
+ * start:250,
+ * end:500,
+ * style:{
+ * fillColor:"#00EE00"
+ * }
+ * },
+ * {
+ * start:500,
+ * end:750,
+ * style:{
+ * fillColor:"#00FF7F"
+ * }
+ * },
+ * {
+ * start:750,
+ * end:1500,
+ * style:{
+ * fillColor:"#00FF00"
+ * }
+ * }
+ * ]
+ */
+
+class Bar3D extends Graph {
+
+ constructor(data, layer, fields, setting, lonlat, option) {
+ super(data, layer, fields, setting, lonlat, option);
+ this.CLASS_NAME = "Zondy.Theme.Bar3D";
+ }
+
+ /**
+ * @function Zondy.Theme.Bar3D.prototype.destroy
+ * @override
+ */
+ destroy() {
+ super.destroy();
+ }
+
+ /**
+ * @function Zondy.Theme.Bar3D.prototype.assembleShapes
+ * @description 图形装配实现(扩展接口)。
+ */
+ assembleShapes() {
+ // 图表配置对象
+ var sets = this.setting;
+
+ // 默认数据视图框
+ if (!sets.dataViewBoxParameter) {
+ if (typeof (sets.useAxis) === "undefined" || sets.useAxis) {
+ sets.dataViewBoxParameter = [45, 25, 20, 20];
+ } else {
+ sets.dataViewBoxParameter = [5, 5, 5, 5];
+ }
+ }
+
+ // 3d 柱图的坐标轴默认使用坐标轴箭头
+ sets.axisUseArrow = (typeof (sets.axisUseArrow) !== "undefined") ? sets.axisUseArrow : true;
+ sets.axisXLabelsOffset = (typeof (sets.axisXLabelsOffset) !== "undefined") ? sets.axisXLabelsOffset : [-10, 10];
+
+ // 重要步骤:初始化参数
+ if (!this.initBaseParameter()) {
+ return;
+ }
+
+ // 值域
+ var codomain = this.DVBCodomain;
+ // 重要步骤:定义图表 Bar 数据视图框中单位值的含义
+ this.DVBUnitValue = (codomain[1] - codomain[0]) / this.DVBHeight;
+ // 数据视图域
+ var dvb = this.dataViewBox;
+ // 用户数据值
+ var fv = this.dataValues;
+ if (fv.length < 1) {
+ return;
+ } // 没有数据
+
+ // 数据溢出值域范围处理
+ for (let i = 0, fvLen = fv.length; i < fvLen; i++) {
+ if (fv[i] < codomain[0] || fv[i] > codomain[1]) {
+ return;
+ }
+ }
+
+ // 获取 x 轴上的图形信息
+ var xShapeInfo = this.calculateXShapeInfo();
+ if (!xShapeInfo) {
+ return;
+ }
+ // 每个柱条 x 位置
+ var xsLoc = xShapeInfo.xPositions;
+ // 柱条宽度
+ var xsWdith = xShapeInfo.width;
+
+ // 坐标轴, 默认启用
+ if (typeof (sets.useBackground) === "undefined" || sets.useBackground) {
+ this.shapes.push(ShapeFactory.Background(this.shapeFactory, this.chartBox, sets));
+ }
+
+ // 坐标轴
+ if (!sets.axis3DParameter || isNaN(sets.axis3DParameter) || sets.axis3DParameter < 15) {
+ sets.axis3DParameter = 20;
+ }
+ if (typeof (sets.useAxis) === "undefined" || sets.useAxis) {
+ this.shapes = this.shapes.concat(ShapeFactory.GraphAxis(this.shapeFactory, dvb, sets, xShapeInfo));
+ }
+
+ // 3d 偏移量, 默认值 10;
+ var offset3d = (sets.bar3DParameter && !isNaN(sets.bar3DParameter)) ? sets.bar3DParameter : 10;
+
+ for (let i = 0; i < fv.length; i++) {
+ // 无 3d 偏移量时的柱面顶部 y 坐标
+ var yPx = dvb[1] - (fv[i] - codomain[0]) / this.DVBUnitValue;
+ // 无 3d 偏移量时的柱面的左、右端 x 坐标
+ var iPoiL = xsLoc[i] - xsWdith / 2;
+ var iPoiR = xsLoc[i] + xsWdith / 2;
+
+ // 3d 柱顶面节点
+ var bar3DTopPois = [
+ [iPoiL, yPx],
+ [iPoiR, yPx],
+ [iPoiR - offset3d, yPx + offset3d],
+ [iPoiL - offset3d, yPx + offset3d]
+ ];
+
+ // 3d 柱侧面节点
+ var bar3DSidePois = [
+ [iPoiR, yPx],
+ [iPoiR - offset3d, yPx + offset3d],
+ [iPoiR - offset3d, dvb[1] + offset3d],
+ [iPoiR, dvb[1]]
+ ];
+
+ // 3d 柱正面节点
+ var bar3DFacePois = [
+ [iPoiL - offset3d, dvb[1] + offset3d],
+ [iPoiR - offset3d, dvb[1] + offset3d],
+ [iPoiR - offset3d, yPx + offset3d],
+ [iPoiL - offset3d, yPx + offset3d]
+ ];
+ if (offset3d <= 0) { // offset3d <= 0 时正面不偏移
+ bar3DFacePois = [
+ [iPoiL, dvb[1]],
+ [iPoiR, dvb[1]],
+ [iPoiR, yPx],
+ [iPoiL, yPx]
+ ];
+ }
+
+ // 新建 3d 柱面顶面、侧面、正面图形参数对象
+ var polyTopSP = new FeaturePolygon(bar3DTopPois);
+ var polySideSP = new FeaturePolygon(bar3DSidePois);
+ var polyFaceSP = new FeaturePolygon(bar3DFacePois);
+
+
+ // 侧面、正面图形 style 默认值
+ sets.barSideStyle = sets.barSideStyle ? sets.barSideStyle : sets.barFaceStyle;
+ sets.barSideStyleByFields = sets.barSideStyleByFields ? sets.barSideStyleByFields : sets.barFaceStyleByFields;
+ sets.barSideStyleByCodomain = sets.barSideStyleByCodomain ? sets.barSideStyleByCodomain : sets.barFaceStyleByCodomain;
+ sets.barTopStyle = sets.barTopStyle ? sets.barTopStyle : sets.barFaceStyle;
+ sets.barTopStyleByFields = sets.barTopStyleByFields ? sets.barTopStyleByFields : sets.barFaceStyleByFields;
+ sets.barTopStyleByCodomain = sets.barTopStyleByCodomain ? sets.barTopStyleByCodomain : sets.barFaceStyleByCodomain;
+ // 顶面、侧面、正面图形 style
+ polyFaceSP.style = ShapeFactory.ShapeStyleTool({
+ stroke: true,
+ strokeColor: "#ffffff",
+ fillColor: "#ee9900"
+ },
+ sets.barFaceStyle, sets.barFaceStyleByFields, sets.barFaceStyleByCodomain, i, fv[i]);
+ polySideSP.style = ShapeFactory.ShapeStyleTool({
+ stroke: true,
+ strokeColor: "#ffffff",
+ fillColor: "#ee9900"
+ },
+ sets.barSideStyle, sets.barSideStyleByFields, sets.barSideStyleByCodomain, i, fv[i]);
+ polyTopSP.style = ShapeFactory.ShapeStyleTool({
+ stroke: true,
+ strokeColor: "#ffffff",
+ fillColor: "#ee9900"
+ },
+ sets.barTopStyle, sets.barTopStyleByFields, sets.barTopStyleByCodomain, i, fv[i]);
+
+ // 3d 柱条高亮样式
+ sets.barSideHoverStyle = sets.barSideHoverStyle ? sets.barSideHoverStyle : sets.barFaceHoverStyle;
+ sets.barTopHoverStyle = sets.barTopHoverStyle ? sets.barTopHoverStyle : sets.barFaceHoverStyle;
+ polyFaceSP.highlightStyle = ShapeFactory.ShapeStyleTool({stroke: true}, sets.barFaceHoverStyle);
+ polySideSP.highlightStyle = ShapeFactory.ShapeStyleTool({stroke: true}, sets.barSideHoverStyle);
+ polyTopSP.highlightStyle = ShapeFactory.ShapeStyleTool({stroke: true}, sets.barTopHoverStyle);
+
+ // 图形携带的数据 id 信息 & 高亮模式
+ polyTopSP.refDataID = polySideSP.refDataID = polyFaceSP.refDataID = this.data.FID;
+ // hover 模式(组合)
+ polyTopSP.isHoverByRefDataID = polySideSP.isHoverByRefDataID = polyFaceSP.isHoverByRefDataID = true;
+ // 高亮组(当鼠标 hover 到组内任何一个图形,整个组的图形都会高亮。refDataHoverGroup 在 isHoverByRefDataID 为 true 时有效)
+ polyTopSP.refDataHoverGroup = polySideSP.refDataHoverGroup = polyFaceSP.refDataHoverGroup = newGuid();
+ // 图形携带的数据信息
+ polyTopSP.dataInfo = polySideSP.dataInfo = polyFaceSP.dataInfo = {
+ field: this.fields[i],
+ value: fv[i]
+ };
+
+ // 3d 柱条顶面、侧面、正面图形 hover click 设置
+ if (typeof (sets.barHoverAble) !== "undefined") {
+ polyTopSP.hoverable = polySideSP.hoverable = polyFaceSP.hoverable = sets.barHoverAble;
+ }
+ if (typeof (sets.barClickAble) !== "undefined") {
+ polyTopSP.clickable = polySideSP.clickable = polyFaceSP.clickable = sets.barClickAble;
+ }
+
+ // 创建3d 柱条的顶面、侧面、正面图形并添加到图表的图形列表数组
+ this.shapes.push(this.shapeFactory.createShape(polySideSP));
+ this.shapes.push(this.shapeFactory.createShape(polyTopSP));
+ this.shapes.push(this.shapeFactory.createShape(polyFaceSP));
+ }
+
+ // 重要步骤:将图形转为由相对坐标表示的图形,以便在地图平移缩放过程中快速重绘图形
+ // (统计专题图模块从结构上要求使用相对坐标,assembleShapes() 函数必须在图形装配完成后调用 shapesConvertToRelativeCoordinate() 函数)
+ this.shapesConvertToRelativeCoordinate();
+ }
+
+ /**
+ * @function Zondy.Theme.Bar3D.prototype.calculateXShapeInfo
+ * @description 计算 X 轴方向上的图形信息,此信息是一个对象,包含两个属性,
+ * 属性 xPositions 是一个一维数组,该数组元素表示图形在 x 轴方向上的像素坐标值,
+ * 如果图形在 x 方向上有一定宽度,通常取图形在 x 方向上的中心点为图形在 x 方向上的坐标值。
+ * width 表示图形的宽度(特别注意:点的宽度始终为 0,而不是其直径)。
+ * 本函数中图形配置对象 setting 可设属性:
+ * xShapeBlank - {Array.} 水平方向上的图形空白间隔参数。
+ * 长度为 3 的数组,第一元素表示第一个图形左端与数据视图框左端的空白间距,第二个元素表示图形间空白间距,
+ * 第三个元素表示最后一个图形右端与数据视图框右端端的空白间距 。
+ * @returns {Object} 如果计算失败,返回 null;如果计算成功,返回 X 轴方向上的图形信息,此信息是一个对象,包含以下两个属性:
+ * xPositions - {Array.} 表示图形在 x 轴方向上的像素坐标值,如果图形在 x 方向上有一定宽度,通常取图形在 x 方向上的中心点为图形在 x 方向上的坐标值。
+ * width - {number} 表示图形的宽度(特别注意:点的宽度始终为 0,而不是其直径)。
+ */
+ calculateXShapeInfo() {
+ var dvb = this.dataViewBox; // 数据视图框
+ var sets = this.setting; // 图表配置对象
+ var fvc = this.dataValues.length; // 数组值个数
+
+ if (fvc < 1) {
+ return null;
+ }
+
+ var xBlank; // x 轴空白间隔参数
+ var xShapePositions = []; // x 轴上图形的位置
+ var xShapeWidth = 0; // x 轴上图形宽度(自适应)
+ var dvbWidth = this.DVBWidth; // 数据视图框宽度
+
+ // x 轴空白间隔参数处理
+ if (sets.xShapeBlank && sets.xShapeBlank.length && sets.xShapeBlank.length === 3) {
+ xBlank = sets.xShapeBlank;
+ var xsLen = dvbWidth - (xBlank[0] + xBlank[2] + (fvc - 1) * xBlank[1])
+ if (xsLen <= fvc) {
+ return null;
+ }
+ xShapeWidth = xsLen / fvc
+ } else {
+ // 默认使用等距离空白间隔,空白间隔为图形宽度
+ xShapeWidth = dvbWidth / (2 * fvc + 1);
+ xBlank = [xShapeWidth, xShapeWidth, xShapeWidth];
+ }
+
+ // 图形 x 轴上的位置计算
+ var xOffset = 0;
+ for (var i = 0; i < fvc; i++) {
+ if (i === 0) {
+ xOffset = xBlank[0] + xShapeWidth / 2;
+ } else {
+ xOffset += (xShapeWidth + xBlank[1]);
+ }
+
+ xShapePositions.push(dvb[0] + xOffset);
+ }
+
+ return {
+ "xPositions": xShapePositions,
+ "width": xShapeWidth
+ };
+ }
+}
+
+export {Bar3D};
+Zondy.Theme.Bar3D = Bar3D;
\ No newline at end of file
diff --git a/src/common/overlay/Circle.js b/src/common/overlay/Circle.js
new file mode 100644
index 000000000..d16285b08
--- /dev/null
+++ b/src/common/overlay/Circle.js
@@ -0,0 +1,153 @@
+import {Zondy} from '../../service/common/Base';
+import {Theme as FeatureTheme} from './feature/Theme';
+import {Circle as FeatureCircle} from './feature/Circle';
+import {ShapeFactory} from './feature/ShapeFactory';
+import {RankSymbol} from './RankSymbol';
+
+/**
+ * @private
+ * @class Zondy.Theme.Circle
+ * @classdesc 圆类。
+ * @extends Zondy.Theme.RankSymbol
+ * @param {Zondy.Vector} data - 用户数据。
+ * @param {Zondy.Layer.RankSymbol} layer - 此专题要素所在图层。
+ * @param {Array.} fields - data 中的参与此图表生成的字段名称。
+ * @param {Zondy.Theme.Circle.setting} setting - 图表配置对象。
+ * @param {Zondy.LonLat} [lonlat] - 专题要素地理位置,默认为 data 指代的地理要素 Bounds 中心。
+ *
+ * @typedef {Object} Zondy.Theme.Circle.setting
+ * @property {Array.} codomain - 图表允许展示的数据值域,长度为 2 的一维数组,第一个元素表示值域下限,第二个元素表示值域上限。
+ * @property {number} [maxR] - 圆形的最大半径。
+ * @property {number} [minR] - 圆形的最小半径。
+ * @property {string} [fillColor] - 圆形的填充色,如:fillColor: "#FFB980"。
+ * @property {Object} [circleStyle] - 圆形的基础 style,此参数控制圆形基础样式,优先级低于 circleStyleByFields 和 circleStyleByCodomain。
+ * @property {number} [decimalNumber] - 数据值数组 dataValues 元素值小数位数,数据的小数位处理参数,取值范围:[0, 16]。如果不设置此参数,在取数据值时不对数据做小数位处理。
+ * @property {Object} [circleHoverStyle] - 圆形 hover 状态时的样式,circleHoverAble 为 true 时有效。
+ * @property {boolean} [circleHoverAble=true] - 是否允许圆形使用 hover 状态。同时设置 circleHoverAble 和 circleClickAble 为 false,可以直接屏蔽图形对专题图层事件的响应。
+ * @property {boolean} [circleClickAble=true] - 是否允许圆形被点击。同时设置 circleHoverAble 和 circleClickAble 为 false,可以直接屏蔽图形对专题图层事件的响应。
+ */
+class Circle extends RankSymbol {
+
+ constructor(data, layer, fields, setting, lonlat, option) {
+ super(data, layer, fields, setting, lonlat, option);
+ this.CLASS_NAME = "Zondy.Theme.Circle";
+ }
+
+ /**
+ * @function Zondy.Theme.Circle.prototype.destroy
+ * @override
+ */
+ destroy() {
+ super.destroy();
+ }
+
+ /**
+ * @function Zondy.Theme.Circle.prototype.assembleShapes
+ * @description 装配图形(扩展接口)。
+ */
+ assembleShapes() {
+ //默认填充颜色
+ var defaultFillColor = "#ff9277";
+
+ // setting 属性是否已成功赋值
+ if (!this.setting) {
+ return false;
+ }
+ var sets = this.setting;
+ // 检测 setting 的必设参数
+ if (!(sets.codomain)) {
+ return false;
+ }
+
+ // 数据
+ var decimalNumber = (typeof (sets.decimalNumber) !== "undefined" && !isNaN(sets.decimalNumber)) ? sets.decimalNumber : -1;
+ var dataEffective = FeatureTheme.getDataValues(this.data, this.fields, decimalNumber);
+ this.dataValues = dataEffective ? dataEffective : [];
+
+ // 数据值数组
+ var fv = this.dataValues;
+ //if(fv.length != 1) return; // 没有数据 或者数据不唯一
+ //if(fv[0] < 0) return; //数据为负值
+
+ //用户应该定义最大 最小半径 默认最大半径MaxR:100 最小半径MinR:0;
+ if (!sets.maxR) {
+ sets.maxR = 100;
+ }
+ if (!sets.minR) {
+ sets.minR = 0;
+ }
+
+ // 值域范围
+ var codomain = this.DVBCodomain;
+
+ // 重要步骤:定义Circle数据视图框中单位值的含义,单位值:1所代表的长度
+ // 用户定义了值域范围
+ if (codomain && codomain[1] - codomain[0] > 0) {
+ this.DVBUnitValue = sets.maxR / (codomain[1] - codomain[0]);
+ } else {
+ //this.DVBUnitValue = sets.maxR / maxValue;
+ this.DVBUnitValue = sets.maxR;
+ }
+
+ var uv = this.DVBUnitValue;
+ //圆半径
+ var r = fv[0] * uv + sets.minR;
+ this.width = 2 * r;
+ this.height = 2 * r;
+
+ // 重要步骤:初始化参数
+ if (!this.initBaseParameter()) {
+ return;
+ }
+
+ //假如用户设置了值域范围 没有在值域范围直接返回
+ if (codomain) {
+ if (fv[0] < codomain[0] || fv[0] > codomain[1]) {
+ return;
+ }
+ }
+
+ var dvbCenter = this.DVBCenterPoint; // 数据视图框中心作为圆心
+
+ //圆形对象参数
+ var circleSP = new FeatureCircle(dvbCenter[0], dvbCenter[1], r);
+
+ //circleSP.sytle 初始化
+ circleSP.style = ShapeFactory.ShapeStyleTool(null, sets.circleStyle, null, null, 0);
+ //图形的填充颜色
+ if (typeof (sets.fillColor) !== "undefined") {
+ //用户自定义
+ circleSP.style.fillColor = sets.fillColor;
+ } else {
+ //当前默认
+ circleSP.style.fillColor = defaultFillColor;
+ }
+ //圆形 Hover样式
+ circleSP.highlightStyle = ShapeFactory.ShapeStyleTool(null, sets.circleHoverStyle);
+ //圆形 Hover 与 click 设置
+ if (typeof (sets.circleHoverAble) !== "undefined") {
+ circleSP.hoverable = sets.circleHoverAble;
+ }
+ if (typeof (sets.circleClickAble) !== "undefined") {
+ circleSP.clickable = sets.circleClickAble;
+ }
+
+ //图形携带的数据信息
+ circleSP.refDataID = this.data.FID;
+ circleSP.dataInfo = {
+ field: this.fields[0],
+ r: r,
+ value: fv[0]
+ };
+
+ // 创建扇形并把此扇形添加到图表图形数组
+ this.shapes.push(this.shapeFactory.createShape(circleSP));
+
+ // 重要步骤:将图形转为由相对坐标表示的图形,以便在地图平移缩放过程中快速重绘图形
+ // (统计专题图模块从结构上要求使用相对坐标,assembleShapes() 函数必须在图形装配完成后调用 shapesConvertToRelativeCoordinate() 函数)
+ this.shapesConvertToRelativeCoordinate();
+ }
+}
+
+export {Circle};
+Zondy.Theme.Circle = Circle;
\ No newline at end of file
diff --git a/src/common/overlay/Graph.js b/src/common/overlay/Graph.js
new file mode 100644
index 000000000..d6a59383b
--- /dev/null
+++ b/src/common/overlay/Graph.js
@@ -0,0 +1,591 @@
+import {Zondy} from '../../service/common/Base';
+import {Theme} from './feature/Theme';
+import {ShapeFactory} from './feature/ShapeFactory';
+import {Rectangle} from '../../service/common/Rectangle';
+
+/**
+ * @private
+ * @class Zondy.Theme.Graph
+ * @classdesc 统计专题要素基类。
+ * @description 此类定义了统计专题要素基础模型,具体的图表模型通过继承此类,在子类中实现 assembleShapes 方法。
+ * 统计专题要素模型采用了可视化图形大小自适应策略,用较少的参数控制着图表诸多图形,图表配置对象 的基础属性只有 7 个,
+ * 它们控制着图表结构、值域范围、数据小数位等基础图表形态。构成图表的图形必须在图表结构里自适应大小。
+ * 此类不可实例化,此类的可实例化子类必须实现 assembleShapes() 方法。
+ * @extends Zondy.Theme
+ * @param {Zondy.Vector} data - 用户数据。
+ * @param {Zondy.Layer.Theme} layer - 此专题要素所在图层。
+ * @param {Array.} fields - data 中的参与此图表生成的字段名称。
+ * @param {Object} setting - 图表配置对象。
+ * @param {Zondy.LonLat} [lonlat] - 专题要素地理位置。默认为 data 指代的地理要素 Bounds 中心。
+ */
+class Graph extends Theme {
+
+
+ constructor(data, layer, fields, setting, lonlat, options) {
+ super(data, layer, fields, setting, lonlat, options);
+
+ /**
+ * @member {Zondy.ShapeFactory} Zondy.Theme.Graph.prototype.shapeFactory
+ * @description 内置的图形工厂对象,调用其 createShape 方法创建图形。
+ */
+ this.shapeFactory = new ShapeFactory();
+
+ /**
+ * @member {Object} Zondy.Theme.Graph.prototype.shapeParameters
+ * @description 当前图形参数对象, 的子类对象。
+ */
+ this.shapeParameters = null;
+
+ /**
+ * @member {boolean} [Zondy.Theme.Graph.prototype.RelativeCoordinate]
+ * @description 图形是否已经计算了相对坐标。
+ */
+ this.RelativeCoordinate = false;
+
+ /**
+ * @member {Object} Zondy.Theme.Graph.prototype.setting
+ * @description 图表配置对象,该对象控制着图表的可视化显示。
+ * @param {number} width - 专题要素(图表)宽度。
+ * @param {number} height - 专题要素(图表)高度。
+ * @param {Array.} codomain - 值域,长度为 2 的一维数组,第一个元素表示值域下限,第二个元素表示值域上限。
+ * @param {number} [XOffset] - 专题要素(图表)在 X 方向上的偏移值,单位像素。
+ * @param {number} [YOffset] - 专题要素(图表)在 Y 方向上的偏移值,单位像素。
+ * @param {Array.} [dataViewBoxParameter] - 数据视图框 dataViewBox 参数,它是指图表框 chartBox
+ * (由图表位置、图表宽度、图表高度构成的图表范围框)在左、下,右,上四个方向上的内偏距值。
+ * @param {number} [decimalNumber] - 数据值数组 dataValues 元素值小数位数,数据的小数位处理参数,取值范围:[0, 16]。
+ * 如果不设置此参数,在取数据值时不对数据做小数位处理。
+ *
+ */
+ this.setting = null;
+
+ /**
+ * @readonly
+ * @member {Array.} Zondy.Theme.Graph.prototype.origonPoint
+ * @description 专题要素(图表)原点,图表左上角点像素坐标,是长度为 2 的一维数组,第一个元素表示 x 坐标,第二个元素表示 y 坐标。
+ */
+ this.origonPoint = null;
+
+ /**
+ * @readonly
+ * @member {Array.} Zondy.Theme.Graph.prototype.chartBox
+ * @description 专题要素(图表)区域,即图表框,长度为 4 的一维数组,数组的 4 个元素依次表示图表框左端 x 坐标值、
+ * 下端 y坐标值、 右端 x坐标值、 上端 y 坐标值;[left, bottom, right, top]。
+ */
+ this.chartBox = null;
+
+ /**
+ * @readonly
+ * @member {Zondy.Bounds} Zondy.Theme.Graph.prototype.chartBounds
+ * @description 图表 Bounds 随着 lonlat、XOffset、YOffset 更新,注意 chartBounds 是图表像素范围,不是地理范围。
+ */
+ this.chartBounds = null;
+
+ /**
+ * @readonly
+ * @member {number} Zondy.Theme.Graph.prototype.width
+ * @description 专题要素(图表)宽度 。
+ */
+ this.width = null;
+
+ /**
+ * @readonly
+ * @member {number} Zondy.Theme.Graph.prototype.height
+ * @description 专题要素(图表)高度 。
+ */
+ this.height = null;
+
+ /**
+ * @readonly
+ * @member {number} Zondy.Theme.Graph.prototype.XOffset
+ * @description 专题要素(图表)在 X 方向上的偏移值,单位像素。
+ */
+ this.XOffset = 0;
+
+ /**
+ * @readonly
+ * @member {number} Zondy.Theme.Graph.prototype.YOffset
+ * @description 专题要素(图表)在 Y 方向上的偏移值,单位像素。
+ */
+ this.YOffset = 0;
+
+ /**
+ * @readonly
+ * @member {Array.} Zondy.Theme.Graph.prototype.DVBParameter
+ * @description 数据视图框参数,长度为 4 的一维数组(数组元素值 >= 0),[leftOffset, bottomOffset, rightOffset, topOffset],chartBox 内偏距值。
+ * 此属性用于指定数据视图框 dataViewBox 的范围。
+ */
+ this.DVBParameter = null;
+
+ /**
+ * @readonly
+ * @member {Array.} Zondy.Theme.Graph.prototype.dataViewBox
+ * @description 数据视图框,长度为 4 的一维数组,[left, bottom, right, top]。
+ * dataViewBox 是统计专题要素最核心的内容,它负责解释数据在一个像素区域里的数据可视化含义,
+ * 这种含义用可视化图形表达出来,这些表示数据的图形和一些辅助图形组合在一起构成统计专题图表。
+ */
+ this.dataViewBox = null;
+
+ /**
+ * @readonly
+ * @member {Array.} Zondy.Theme.Graph.prototype.DVBCodomain
+ * @description 数据视图框的内允许展示的数据值域,长度为 2 的一维数组,第一个元素表示值域下限,第二个元素表示值域上限。
+ * dataViewBox 中允许的数据范围,对数据溢出值域范围情况的处理需要在 assembleShapes 中进行。
+ */
+ this.DVBCodomain = null;
+
+ /**
+ * @readonly
+ * @member {Array.} Zondy.Theme.Graph.prototype.DVBCenterPoint
+ * @description 数据视图框中心点,长度为 2 的一维数组,第一个元素表示 x 坐标,第二个元素表示 y 坐标。
+ */
+ this.DVBCenterPoint = null;
+
+ /**
+ * @readonly
+ * @member {string} Zondy.Theme.Graph.prototype.DVBUnitValue
+ * @description 单位值。在 assembleShapes() 中初始化其具体意义,例如:饼图的 DVBUnitValue 可以定义为"360/数据总和",
+ * 折线图的 DVBUnitValue 可以定义为 "DVBCodomain/DVBHeight"。
+ */
+ this.DVBUnitValue = null;
+
+ /**
+ * @readonly
+ * @member {Array.} Zondy.Theme.Graph.prototype.DVBOrigonPoint
+ * @description 数据视图框原点,数据视图框左上角点,长度为 2 的一维数组,第一个元素表示 x 坐标,第二个元素表示 y 坐标。
+ */
+ this.DVBOrigonPoint = null;
+
+ /**
+ * @readonly
+ * @member {number} Zondy.Theme.Graph.prototype.DVBWidth
+ * @description 数据视图框宽度。
+ */
+ this.DVBWidth = null;
+
+ /**
+ * @readonly
+ * @member {number} Zondy.Theme.Graph.prototype.DVBHeight
+ * @description 数据视图框高度。
+ */
+ this.DVBHeight = null;
+
+ /**
+ * @readonly
+ * @member {Array.} Zondy.Theme.Graph.prototype.origonPointOffset
+ * @description 数据视图框原点相对于图表框的原点偏移量,长度为 2 的一维数组,第一个元素表示 x 偏移量,第二个元素表示 y 偏移量。
+ */
+ this.origonPointOffset = null;
+
+ /**
+ * @readonly
+ * @member {Array.} Zondy.Theme.Graph.prototype.fields
+ * @description 数据{Zondy.Vector}属性字段。
+ */
+ this.fields = fields || [];
+
+ /**
+ * @readonly
+ * @member {Array.} Zondy.Theme.Graph.prototype.dataValues
+ * @description 图表展示的数据值,通过 fields 从数据 feature 属性中获得。
+ */
+ this.dataValues = null;
+ // 图表位置
+ if (lonlat) {
+ this.lonlat = lonlat;
+ } else {
+ // 默认使用 bounds 中心
+ if (options && options.calGravity) {
+ if (data.ftype === Zondy.Enum.FeatureType.Pnt) {
+ var centerDot = data.fGeom.PntGeom[0].Dot
+ this.lonlat = [centerDot.x, centerDot.y]
+ } else if (data.ftype === Zondy.Enum.FeatureType.Lin) {
+ var arcs = data.fGeom.LinGeom[0].Line.Arcs
+ var points = []
+ arcs.forEach(function (item) {
+ const dots = item.Dots
+ points = [].concat(dots)
+ })
+ var index = Math.ceil(points.length / 2) // 如果是多个点 则取中间范围的一个点
+ var centerDot = points[index - 1]
+ this.lonlat = [centerDot.x, centerDot.y]
+ } else if (data.ftype === Zondy.Enum.FeatureType.Reg) {
+ this.lonlat = this.getCenterOfGravityPoint(data);
+ } else {
+ var dataBounds = data.bound;
+ this.lonlat = [(dataBounds.xmin + dataBounds.xmax) / 2.0, (dataBounds.ymin + dataBounds.ymax) / 2.0];
+ }
+ } else {
+ var dataBounds = data.bound;
+ this.lonlat = [(dataBounds.xmin + dataBounds.xmax) / 2.0, (dataBounds.ymin + dataBounds.ymax) / 2.0];
+ }
+ }
+
+ // 配置项检测与赋值
+ if (setting && setting.width && setting.height && setting.codomain) {
+ this.setting = setting;
+ }
+ this.CLASS_NAME = "Zondy.Feature.Theme.Graph";
+
+ }
+
+ /**
+ * @function Zondy.Theme.Graph.prototype.destroy
+ * @description 销毁专题要素。
+ */
+ destroy() {
+ this.shapeFactory = null;
+ this.shapeParameters = null;
+ this.width = null;
+ this.height = null;
+ this.origonPoint = null;
+ this.chartBox = null;
+ this.dataViewBox = null;
+ this.chartBounds = null;
+ this.DVBParameter = null;
+ this.DVBOrigonPoint = null;
+ this.DVBCenterPoint = null;
+ this.DVBWidth = null;
+ this.DVBHeight = null;
+ this.DVBCodomain = null;
+ this.DVBUnitValue = null;
+ this.origonPointOffset = null;
+ this.XOffset = null;
+ this.YOffset = null;
+ this.fields = null;
+ this.dataValues = null;
+ this.setting = null;
+ super.destroy();
+ }
+
+ /**
+ * @function Zondy.Theme.Graph.prototype.initBaseParameter
+ * @description 初始化专题要素(图表)基础参数。在调用此方法前,此类的图表模型相关属性都是不可用的 ,此方法在 assembleShapes 函数中调用。
+ * 调用此函数关系到 setting 对象的以下属性。
+ * @param {number} width - 专题要素(图表)宽度。
+ * @param {number} height - 专题要素(图表)高度。
+ * @param {Array.} codomain - 值域,长度为 2 的一维数组,第一个元素表示值域下限,第二个元素表示值域上限。
+ * @param {number} [XOffset] - 专题要素(图表)在 X 方向上的偏移值,单位像素。
+ * @param {number} [YOffset] - 专题要素(图表)在 Y 方向上的偏移值,单位像素。
+ * @param {Array.} [dataViewBoxParameter] - 数据视图框 dataViewBox 参数,它是指图表框 chartBox。
+ * (由图表位置、图表宽度、图表高度构成的图表范围框)在左、下,右,上四个方向上的内偏距值。
+ * @param {number} [decimalNumber] - 数据值数组 dataValues 元素值小数位数,数据的小数位处理参数,取值范围:[0, 16]。如果不设置此参数,在取数据值时不对数据做小数位处理。
+ * @returns {boolean} 初始化参数是否成功。
+ */
+ initBaseParameter() {
+ // 参数初始化是否成功
+ var isSuccess = true;
+
+ // setting 属性是否已成功赋值
+ if (!this.setting) {
+ return false;
+ }
+ var sets = this.setting;
+ // 检测 setting 的必设参数
+ if (!(sets.width && sets.height && sets.codomain)) {
+ return false;
+ }
+
+ // 数据
+ var decimalNumber = (typeof (sets.decimalNumber) !== "undefined" && !isNaN(sets.decimalNumber)) ? sets.decimalNumber : -1;
+ var dataEffective = Theme.getDataValues(this.data, this.fields, decimalNumber);
+ this.dataValues = dataEffective ? dataEffective : [];
+
+ // 基础参数 width, height, codomain
+ this.width = parseFloat(sets.width);
+ this.height = parseFloat(sets.height);
+ this.DVBCodomain = sets.codomain;
+
+ // 图表偏移
+ // if(sets.XOffset) {this.XOffset = sets.XOffset};
+ // if(sets.YOffset) {this.YOffset = sets.YOffset};
+ this.XOffset = sets.XOffset ? sets.XOffset : 0;
+ this.YOffset = sets.YOffset ? sets.YOffset : 0;
+
+ // 其他默认值
+ this.origonPoint = [];
+ this.chartBox = [];
+ this.dataViewBox = [];
+
+ this.DVBParameter = sets.dataViewBoxParameter ? sets.dataViewBoxParameter : [0, 0, 0, 0];
+
+ this.DVBOrigonPoint = [];
+ this.DVBCenterPoint = [];
+ this.origonPointOffset = [];
+
+ // 图表位置
+ this.resetLocation();
+
+ // 专题要素宽度 w
+ var w = this.width;
+ // 专题要素高度 h
+ var h = this.height;
+ // 专题要素像素位置 loc
+ var loc = this.location;
+
+ // 专题要素像素位置 loc
+ this.origonPoint = [loc[0] - w / 2, loc[1] - h / 2];
+ // 专题要素原点(左上角)
+ var op = this.origonPoint;
+
+ // 图表框([left, bottom, right, top])
+ this.chartBox = [op[0], op[1] + h, op[0] + w, op[1]];
+ // 图表框
+ var cb = this.chartBox;
+
+ // 数据视图框参数,它是图表框各方向对应的内偏距
+ var dbbP = this.DVBParameter;
+ // 数据视图框 ([left, bottom, right, top])
+ this.dataViewBox = [cb[0] + dbbP[0], cb[1] - dbbP[1], cb[2] - dbbP[2], cb[3] + dbbP[3]];
+ // 数据视图框
+ var dvb = this.dataViewBox;
+ //检查数据视图框是否合法
+ if (dvb[0] >= dvb[2] || dvb[1] <= dvb[3]) {
+ return false;
+ }
+
+ // 数据视图框原点
+ this.DVBOrigonPoint = [dvb[0], dvb[3]];
+ // 数据视图框宽度
+ this.DVBWidth = Math.abs(dvb[2] - dvb[0]);
+ // 数据视图框高度
+ this.DVBHeight = Math.abs(dvb[1] - dvb[3]);
+ // 数据视图框中心点
+ this.DVBCenterPoint = [this.DVBOrigonPoint[0] + this.DVBWidth / 2, this.DVBOrigonPoint[1] + this.DVBHeight / 2]
+
+ // 数据视图框原点与图表框的原点偏移量
+ this.origonPointOffset = [this.DVBOrigonPoint[0] - op[0], this.DVBOrigonPoint[1] - op[1]];
+
+ return isSuccess;
+ }
+
+ /**
+ * @function Zondy.Theme.Graph.prototype.resetLocation
+ * @description 根据地理位置 lonlat 重置专题要素(图表)位置。
+ * @param {Zondy.LonLat} lonlat - 专题要素新的像素中心位置。
+ * @returns {Array.} - 新专题要素像素参考位置。长度为 2 的数组,第一个元素表示 x 坐标,第二个元素表示 y 坐标。
+ */
+ resetLocation(lonlat) {
+ if (lonlat) {
+ this.lonlat = lonlat;
+ }
+
+ // 获取地理位置对应的像素坐标 newLocalLX
+ var newLocalLX = this.getLocalXY(this.lonlat);
+ // 处理偏移量 XOffset, YOffset
+ newLocalLX[0] += this.XOffset;
+ newLocalLX[1] += this.YOffset;
+ // 将图形位置赋予 location 属性(注意 location 属性表示的是专题要素中心位置)
+ this.location = newLocalLX;
+
+ // 更新图表像素 Bounds
+ var w = this.width;
+ var h = this.height;
+ var loc = this.location;
+ this.chartBounds = new Rectangle(loc[0] - w / 2, loc[1] - h / 2, loc[0] + w / 2, loc[1] + h / 2);
+
+ //重新计算当前渐变色
+ this.resetLinearGradient();
+
+ return loc;
+ }
+
+ /**
+ * @function Zondy.Theme.Graph.prototype.resetLinearGradient
+ * @description resetLocation 中调用 图表的相对坐标存在的时候,重新计算渐变的颜色(目前用于二维柱状图渐变色 所以子类实现此方法)。
+ */
+ resetLinearGradient() {
+ //子类实现此方法
+ }
+
+ /**
+ * @function Zondy.Theme.Graph.prototype.shapesConvertToRelativeCoordinate
+ * @description 将(构成图表)图形的节点转为相对坐标表示,此函数必须且只能在 assembleShapes() 结束时调用。
+ */
+ shapesConvertToRelativeCoordinate() {
+ var shapes = this.shapes;
+ var shapeROP = this.location;
+ for (var i = 0, len = shapes.length; i < len; i++) {
+ shapes[i].refOriginalPosition = shapeROP;
+
+ var style = shapes[i].style;
+
+ for (var sty in style) {
+ switch (sty) {
+ case "pointList":
+ var pl = style[sty];
+ for (var j = 0, len2 = pl.length; j < len2; j++) {
+ pl[j][0] -= shapeROP[0];
+ pl[j][1] -= shapeROP[1];
+ }
+ break;
+ case "x":
+ style[sty] -= shapeROP[0];
+ break;
+ case "y":
+ style[sty] -= shapeROP[1];
+ break;
+ default:
+ break;
+ }
+ }
+ }
+ this.RelativeCoordinate = true;
+ }
+
+ /**
+ * @function Zondy.Theme.Graph.prototype.assembleShapes
+ * @description 图形装配函数。抽象方法,可视化子类必须实现此方法。
+ * 重写此方法的步骤:
+ * 1. 图表的某些特殊配置项(setting)处理,例如多数图表模型需要重新指定 dataViewBoxParameter 的默认值。
+ * 2. 调用 initBaseParameter() 方法初始化模型属性值,此步骤必须执行,只有当 initBaseParameter 返回 true 时才可以允许进行后续步骤。
+ * 3. 计算图形参数,制作图形,图形组合。在组装图表过程中,应该特别注意数据视图框单位值的定义、数据值溢出值域范围的处理和图形大小自适应。
+ * 4. 调用 shapesConvertToRelativeCoordinate() 方法,将图形的坐标值转为相对坐标,此步骤必须执行。
+ * @example
+ * //子类实现 assembleShapes() 接口的步骤示例:
+ * assembleShapes: function(){
+ * // 第一步:图表的某些特殊配置项(setting)处理,例如多数图表模型需要重新指定 dataViewBoxParameter 的默认值。此步骤是非必须过程。
+ *
+ * // 图表配置对象
+ * var sets = this.setting;
+ * // 默认数据视图框,这里展示在使用坐标轴和不使用坐标轴情况下对数据视图框参数赋予不同的默认值
+ * if(!sets.dataViewBoxParameter){
+ * if(typeof(sets.useAxis) === "undefined" || sets.useAxis){
+ * sets.dataViewBoxParameter = [45, 15, 15, 15];
+ * }
+ * else{
+ * sets.dataViewBoxParameter = [5, 5, 5, 5];
+ * }
+ * }
+ *
+ * // 第二步:初始化图表模型基本参数,只有在图表模型基本参数初始化成功时才可模型相关属性,如 this.dataViewBox、 this.DVBCodomain等。此步骤是必须过程。
+ * if(!this.initBaseParameter()) return;
+ *
+ * // 第三步:用图形组装图表,在组装图表过程中,应该特别注意数据视图框单位值的定义、数据值溢出值域范围的处理和图形大小自适应。
+ * // 定义图表数据视图框中单位值的含义,下面行代码表示将数据视图框单位值定义为数据视图框高度上每像素代表的数据值
+ * this.DVBUnitValue = (this.codomain[1] - this.codomain[0])/this.DVBHeight;
+ * var uv = this.DVBUnitValue;
+ *
+ * // 图形参数计算代码......
+ *
+ * // 关于图形装配,实际上就是利用图形工程对象 this.shapeFactory 的 createShape() 方法通过图形参数对象创建可视化的图形对象,并把这些图形对象按序添加到模型的图形库(his.shapes)中。下面的代码演示创建一个面图形参数对象,并允许通过图形配置对象设置图形的 style 和 highlightStyle,
+ * var barParams = new Zondy.ShapeParameters.Polygon(poiLists);
+ * barParams.style = sets.barStyle? sets.barStyle:{fillColor: "lightblue"};
+ * barParams.highlightStyle = sets.barHoverStyle? sets.barHoverStyle:{fillColor: "blue"};
+ * // 图形携带数据ID信息
+ * barParams.refDataID = this.data.id;
+ * // 创建图形并添加到图表图形数组中
+ * this.shapes.push(this.shapeFactory.createShape(barParams));
+ *
+ * // 第四步:调用 shapesConvertToRelativeCoordinate() 方法,将图形库(his.shapes)中的图形转为由相对坐标表示的图形,客户端统计专题图模块从结构上要求可视化图形使用相对坐标,assembleShapes() 函数必须在图形装配完成后调用 shapesConvertToRelativeCoordinate() 函数。此步骤是必须过程。
+ * this.shapesConvertToRelativeCoordinate();
+ * },
+ */
+ assembleShapes() {
+ //子类必须实现此方法
+ }
+
+ /**
+ * @function Zondy.Theme.Graph.prototype.getLocalXY
+ * @description 地理坐标转为像素坐标。
+ * @param {Zondy.Lonlat} lonlat - 带转换的地理坐标。
+ * @returns 屏幕像素坐标。
+ */
+ getLocalXY(lonlat) {
+ return this.layer.getLocalXY(lonlat);
+ }
+
+ getCenterOfGravityPoint(fea) {
+ var mPoints = [];
+ var area = 0.0;//多边形面积
+ var Gx = 0.0,
+ Gy = 0.0;// 重心的x、y
+ if (fea.ftype === Zondy.Enum.FeatureType.Reg) {
+ var GRegs = fea.fGeom.RegGeom;
+ for (var m = 0; m < GRegs.length; m++) {
+ var GReg = GRegs[m];
+ if (GReg == null || GReg.Rings == null) {
+ continue;
+ }
+ var GLines = GReg.Rings;
+
+ for (var i = 0; i < GLines.length; i++) {
+ var lin = GLines[i];
+ if (lin == null || lin.Arcs == null) {
+ continue;
+ }
+ var arcs = lin.Arcs;
+ for (var j = 0; j < arcs.length; j++) {
+ var arc = arcs[j];
+ if (arc == null || arc.Dots == null) {
+ continue;
+ }
+ var dots = arc.Dots;
+ for (var k = 0; k < dots.length; k++) {
+ mPoints.push(dots[k]);
+ }
+ }
+ }
+ }
+ }
+ for (var i = 1; i <= mPoints.length; i++) {
+ var iLat = mPoints[(i % mPoints.length)].x;
+ var iLng = mPoints[(i % mPoints.length)].y;
+ var nextLat = mPoints[(i - 1)].x;
+ var nextLng = mPoints[(i - 1)].y;
+ var temp = (iLat * nextLng - iLng * nextLat) / 2.0;
+ area += temp;
+ Gx += temp * (iLat + nextLat) / 3.0;
+ Gy += temp * (iLng + nextLng) / 3.0;
+ }
+ Gx = Gx / area;
+ Gy = Gy / area;
+
+ return [Gx, Gy];
+ };
+}
+
+/**
+ * @function Zondy.Theme.getDataValues
+ * @description 根据字段名数组获取指定数据(feature)的属性值数组。属性值类型必须为 Number。
+ * @param {Zondy.Vector} data - 数据。
+ * @param {Array.} [fields] - 字段名数组。
+ * @param {number} [decimalNumber] - 小数位处理参数,对获取到的属性数据值进行小数位处理。
+ * @returns {Array.} 字段名数组对应的属性数据值数组。
+ */
+Theme.getDataValues = function (data, fields, decimalNumber) {
+ if (!data.attributes) {
+ return false;
+ }
+
+ var fieldsValue = [];
+
+ var attrs = data.attributes;
+ for (var i = 0; i < fields.length; i++) {
+ for (var field in attrs) {
+ if (field !== fields[i]) {
+ continue
+ }
+ if (attrs[field] === null || attrs[field] === undefined) {
+ continue
+ }
+ // 数字转换判断
+ try {
+ if (!isNaN(decimalNumber) && decimalNumber >= 0) {
+ fieldsValue.push(parseFloat(attrs[field].toString()).toFixed(decimalNumber));
+ } else {
+ fieldsValue.push(parseFloat(attrs[field].toString()));
+ }
+ } catch (e) {
+ throw new Error("not a number")
+ }
+ }
+ }
+
+ if (fieldsValue.length === fields.length) {
+ return fieldsValue;
+ } else {
+ return false;
+ }
+};
+export {Graph};
+Zondy.Theme.Graph = Graph;
diff --git a/src/common/overlay/Line.js b/src/common/overlay/Line.js
new file mode 100644
index 000000000..798ff33f9
--- /dev/null
+++ b/src/common/overlay/Line.js
@@ -0,0 +1,297 @@
+import {Zondy} from '../../service/common/Base';
+import {ShapeFactory} from './feature/ShapeFactory';
+import {Point} from './feature/Point';
+import {Line as FeatureLine} from './feature/Line';
+import {Graph} from './Graph';
+
+/**
+ * @private
+ * @class Zondy.Theme.Line
+ * @classdesc 折线图。
+ *
+ * @typedef {Object} Zondy.Theme.Line.setting
+ * @property {number} width - 专题要素(图表)宽度。
+ * @property {number} height - 专题要素(图表)高度。
+ * @property {Array.} codomain - 图表允许展示的数据值域,长度为 2 的一维数组,第一个元素表示值域下限,第二个元素表示值域上限。
+ * @property {number} [XOffset] - 专题要素(图表)在 X 方向上的偏移值,单位像素。
+ * @property {number} [YOffset] - 专题要素(图表)在 Y 方向上的偏移值,单位像素。
+ * @property {Array.} [dataViewBoxParameter] - {Array.} 数据视图框 dataViewBox 参数,
+ * 它是指图表框 chartBox (由图表位置、图表宽度、图表高度构成的图表范围框)在左、下,右,上四个方向上的内偏距值。
+ * 当使用坐标轴时 dataViewBoxParameter 的默认值为:[45, 15, 15, 15];不使用坐标轴时 dataViewBoxParameter 的默认值为:[5, 5, 5, 5]。
+ * @property {number} [decimalNumber] - 数据值数组 dataValues 元素值小数位数,数据的小数位处理参数,取值范围:[0, 16]。如果不设置此参数,在取数据值时不对数据做小数位处理。
+ * @property {boolean} [useBackground] - 是否使用图表背景框。
+ * @property {Zondy.Feature.ShapeParameters.Rectangle.style} backgroundStyle - 背景样式。
+ * @property {Array.} [backgroundRadius=[0, 0, 0, 0]] - 背景框矩形圆角半径,可以用数组分别指定四个角的圆角半径,设:左上、右上、右下、左下角的半径依次为 r1、r2、r3、r4 ,
+ * 则 backgroundRadius 为 [r1、r2、r3、r4 ]。
+ * @property {Array.} xShapeBlank - 水平方向上的图形空白间隔参数。长度为 2 的数组,第一元素表示折线左端点与数据视图框左端的空白间距,
+ * 第二个元素表示折线右端点右端与数据视图框右端端的空白间距。
+ * @property {Zondy.Feature.ShapeParameters.Line.style} [axisStyle] - 坐标轴样式。
+ * @property {boolean} [axisUseArrow=false] - 坐标轴是否使用箭头。
+ * @property {number} [axisYTick=0] - y 轴刻度数量。
+ * @property {Array.} [axisYLabels] - y 轴上的标签组内容,标签顺序沿着数据视图框左面条边自上而下,等距排布。例如:["1000", "750", "500", "250", "0"]。
+ * @property {Zondy.Feature.ShapeParameters.Label.style} [axisYLabelsStyle] - y 轴上的标签组样式。
+ * @property {Array.} [axisYLabelsOffset=0] - y 轴上的标签组偏移量。长度为 2 的数组,数组第一项表示 y 轴标签组横向上的偏移量,向左为正。
+ * 数组第二项表示 y 轴标签组纵向上的偏移量,向下为正。
+ * @property {Array.} [axisXLabels] - x 轴上的标签组内容,标签顺序沿着数据视图框下面条边自左向右排布,例如:["92年", "95年", "99年"]。
+ * 标签排布规则:当标签数量与 xShapeInfo 中的属性 xShapeCenter 数量相同(即标签个数与数据个数相等时), 按照 xShapeCenter 提供的位置排布标签,
+ * 否则沿数据视图框下面条边等距排布标签。
+ * @property {Zondy.Feature.ShapeParameters.Label.style} [axisXLabelsStyle] - x 轴上的标签组样式。
+ * @property {Array.} [axisXLabelsOffset=0] - x 轴上的标签组偏移量。长度为 2 的数组,数组第一项表示 x 轴标签组横向上的偏移量,向左为正;
+ * 数组第二项表示 x 轴标签组纵向上的偏移量,向下为正;
+ * @property {boolean} [useXReferenceLine=true] - 是否使用水平参考线,如果为 true,在 axisYTick 大于 0 时有效,水平参考线是 y 轴刻度在数据视图框里的延伸。
+ * @property {Zondy.Feature.ShapeParameters.Line.style} ]xReferenceLineStyle] - 水平参考线样式。
+ * @property {Zondy.Feature.ShapeParameters.Line.style} [lineStyle] - 折线图中折线 style。
+ * @property {Zondy.Feature.ShapeParameters.Point.style} [pointStyle] - 折线图中折线节点基础 style,此参数控制折线节点基础样式,优先级低于 pointStyleByFields 和 pointStyleByCodomain。
+ * @property {Zondy.Feature.ShapeParameters.Point.style} [pointStyleByFields] - 按专题字段 themeFields()为折线节点赋 style,此参数按字段控制折线节点样式,
+ * 优先级低于 pointStyleByCodomain,高于 pointStyle。此数组中的元素是样式对象。
+ * 此参数中的 style 与 themeFields 中的字段一一对应 。例如: themeFields() 为 ["POP_1992", "POP_1995", "POP_1999"],
+ * pointStyleByFields 为[style1, style2, style3],则在图表中,字段 POP_1992 对应的折线节点使用 style1,字段 POP_1995 对应的折线节点使用 style2 ,字段 POP_1999 对应的折线节点使用 style3。
+ * @property {Array.} pointStyleByCodomain - 按折线节点代表的数据值所在值域范围控制折线节点样式,优先级高于 pointStyle 和 pointStyleByFields。
+ * @property {Object} [pointHoverStyle=true] - 折线节点 hover 状态时的样式,pointHoverAble 为 true 时有效。
+ * @property {boolean} [pointHoverAble=true] - 是否允许折线节点使用 hover 状态。同时设置 pointHoverAble 和 pointClickAble 为 false,可以直接屏蔽折线节点对专题图层事件的响应。
+ * @property {boolean} [pointClickAble=true] - 是否允许折线节点被点击。同时设置 pointHoverAble 和 pointClickAble 为 false,可以直接屏蔽折线节点对专题图层事件的响应。
+ *
+ * @example
+ * // pointStyleByCodomain 参数用法示例
+ * // pointStyleByCodomain 的每个元素是个包含值域信息和与值域对应样式信息的对象,该对象(必须)有三个属性:
+ * // start: 值域值下限(包含);
+ * // end: 值域值上限(不包含);
+ * // style: 数据可视化图形的 style,这个样式对象的可设属性: 。
+ * // pointStyleByCodomain 数组形如:
+ * [
+ * {
+ * start:0,
+ * end:250,
+ * style:{
+ * fillColor:"#00CD00"
+ * }
+ * },
+ * {
+ * start:250,
+ * end:500,
+ * style:{
+ * fillColor:"#00EE00"
+ * }
+ * },
+ * {
+ * start:500,
+ * end:750,
+ * style:{
+ * fillColor:"#00FF7F"
+ * }
+ * },
+ * {
+ * start:750,
+ * end:1500,
+ * style:{
+ * fillColor:"#00FF00"
+ * }
+ * }
+ * ]
+ *
+ * @extends Zondy.Feature.Theme.Graph
+ * @param {Zondy.Feature.Vector} data - 用户数据。
+ * @param {Zondy.Layer.Graph} layer - 此专题要素所在图层。
+ * @param {Array.} fields - data 中的参与此图表生成的字段名称。
+ * @param {Zondy.Theme.Line.setting} setting - 图表配置对象。
+ * @param {Zondy.LonLat} [lonlat] - 专题要素地理位置。默认为 data 指代的地理要素 Bounds 中心。
+ */
+class Line extends Graph {
+
+ constructor(data, layer, fields, setting, lonlat, options) {
+ super(data, layer, fields, setting, lonlat, options);
+ this.CLASS_NAME = "Zondy.Theme.Line";
+ }
+
+ /**
+ * @function Zondy.Theme.Line.prototype.destroy
+ * @override
+ */
+ destroy() {
+ super.destroy();
+ }
+
+ /**
+ * @function Zondy.Theme.Line.prototype.assembleShapes
+ * @description 装配图形(扩展接口)。
+ */
+ assembleShapes() {
+ // 图表配置对象
+ var sets = this.setting;
+
+ // 默认数据视图框
+ if (!sets.dataViewBoxParameter) {
+ if (typeof (sets.useAxis) === "undefined" || sets.useAxis) {
+ sets.dataViewBoxParameter = [45, 15, 15, 15];
+ } else {
+ sets.dataViewBoxParameter = [5, 5, 5, 5];
+ }
+ }
+
+ // 重要步骤:初始化参数
+ if (!this.initBaseParameter()) {
+ return;
+ }
+
+ var dvb = this.dataViewBox;
+
+ // 值域
+ var codomain = this.DVBCodomain;
+ // 重要步骤:定义图表 Bar 数据视图框中单位值的含义
+ this.DVBUnitValue = (codomain[1] - codomain[0]) / this.DVBHeight;
+ var uv = this.DVBUnitValue;
+ // 数据值数组
+ var fv = this.dataValues;
+ if (fv.length < 1) {
+ return;
+ } // 没有数据
+
+ // 获取 x 轴上的图形信息
+ var xShapeInfo = this.calculateXShapeInfo();
+ if (!xShapeInfo) {
+ return;
+ }
+ // 折线每个节点的 x 位置
+ var xsLoc = xShapeInfo.xPositions;
+
+ // 背景框,默认启用
+ if (typeof (sets.useBackground) === "undefined" || sets.useBackground) {
+ // 将背景框图形添加到模型的 shapes 数组,注意添加顺序,后添加的图形在先添加的图形之上。
+ this.shapes.push(ShapeFactory.Background(this.shapeFactory, this.chartBox, sets));
+ }
+
+ // 折线图必须使用坐标轴
+ this.shapes = this.shapes.concat(ShapeFactory.GraphAxis(this.shapeFactory, dvb, sets, xShapeInfo));
+
+ // var isDataEffective = true;
+
+ var xPx; // 折线节点 x 坐标
+ var yPx; // 折线节点 y 坐标
+ var poiLists = []; // 折线节点数组
+
+ var shapePois = []; // 折线节点图形数组
+ for (var i = 0, len = fv.length; i < len; i++) {
+ // 数据溢出值域检查
+ if (fv[i] < codomain[0] || fv[i] > codomain[1]) {
+ // isDataEffective = false;
+ return null;
+ }
+
+ xPx = xsLoc[i];
+ yPx = dvb[1] - (fv[i] - codomain[0]) / uv;
+
+ // 折线节点参数对象
+ var poiSP = new Point(xPx, yPx);
+ // 折线节点 style
+ poiSP.style = ShapeFactory.ShapeStyleTool({fillColor: "#ee9900"}, sets.pointStyle, sets.pointStyleByFields, sets.pointStyleByCodomain, i, fv[i]);
+ // 折线节点 hover 样式
+ poiSP.highlightStyle = ShapeFactory.ShapeStyleTool(null, sets.pointHoverStyle);
+
+ // 折线节点 hover click
+ if (typeof (sets.pointHoverAble) !== "undefined") {
+ poiSP.hoverable = sets.pointHoverAble;
+ }
+ if (typeof (sets.pointClickAble) !== "undefined") {
+ poiSP.clickable = sets.pointClickAble;
+ }
+
+ // 图形携带的数据信息
+ poiSP.refDataID = this.data.FID;
+ poiSP.dataInfo = {
+ field: this.fields[i],
+ value: fv[i]
+ };
+
+ // 创建图形并把此图形添加到折线节点图形数组
+ shapePois.push(this.shapeFactory.createShape(poiSP));
+
+ // 添加折线节点到折线节点数组
+ var poi = [xPx, yPx];
+ poiLists.push(poi);
+ }
+
+ // 折线参数对象
+ var lineSP = new FeatureLine(poiLists);
+ lineSP.style = ShapeFactory.ShapeStyleTool({strokeColor: "#ee9900"}, sets.lineStyle);
+ // 禁止事件响应
+ lineSP.clickable = false;
+ lineSP.hoverable = false;
+ var shapeLine = this.shapeFactory.createShape(lineSP);
+ this.shapes.push(shapeLine);
+
+ // 添加节点到图表图形数组
+ this.shapes = this.shapes.concat(shapePois);
+
+ // // 数据范围检测未通过,清空图形
+ // if (isDataEffective === false) {
+ // this.shapes = [];
+ // }
+
+ // 重要步骤:将图形转为由相对坐标表示的图形,以便在地图平移缩放过程中快速重绘图形
+ // (统计专题图模块从结构上要求使用相对坐标,assembleShapes() 函数必须在图形装配完成后调用 shapesConvertToRelativeCoordinate() 函数)
+ this.shapesConvertToRelativeCoordinate();
+ }
+
+ /**
+ * @function Zondy.Theme.Line.prototype.calculateXShapeInfo
+ * @description 计算 X 轴方向上的图形信息,此信息是一个对象,包含两个属性,
+ * 属性 xPositions 是一个一维数组,该数组元素表示图形在 x 轴方向上的像素坐标值,
+ * 如果图形在 x 方向上有一定宽度,通常取图形在 x 方向上的中心点为图形在 x 方向上的坐标值。
+ * width 表示图形的宽度(特别注意:点的宽度始终为 0,而不是其直径)。
+ * 本函数中图形配置对象 setting 可设属性:
+ * xShapeBlank - {Array.} 水平方向上的图形空白间隔参数。
+ * 长度为 2 的数组,第一元素表示第折线左端点与数据视图框左端的空白间距,第二个元素表示折线右端点右端与数据视图框右端端的空白间距 。
+ * @returns {Object} 如果计算失败,返回 null;如果计算成功,返回 X 轴方向上的图形信息,此信息是一个对象,包含以下两个属性:
+ * xPositions - {Array.} 表示图形在 x 轴方向上的像素坐标值,如果图形在 x 方向上有一定宽度,通常取图形在 x 方向上的中心点为图形在 x 方向上的坐标值。
+ * width - {number} 表示图形的宽度(特别注意:点的宽度始终为 0,而不是其直径)。
+ */
+ calculateXShapeInfo() {
+ var dvb = this.dataViewBox; // 数据视图框
+ var sets = this.setting; // 图表配置对象
+ var fvc = this.dataValues.length; // 数组值个数
+
+ if (fvc < 1) {
+ return null;
+ }
+
+ var xBlank; // x 轴空白间隔参数
+ var xShapePositions = []; // x 轴上图形的位置
+ var xShapeWidth = 0; // x 轴上图形宽度(自适应)
+ var dvbWidth = this.DVBWidth; // 数据视图框宽度
+ var unitOffset = 0; // 单位偏移量
+
+ // x 轴空白间隔参数处理
+ if (sets.xShapeBlank && sets.xShapeBlank.length && sets.xShapeBlank.length === 2) {
+ xBlank = sets.xShapeBlank;
+ var xsLen = dvbWidth - (xBlank[0] + xBlank[1]);
+ if (xsLen <= fvc) {
+ return null;
+ }
+ unitOffset = xsLen / (fvc - 1);
+ } else {
+ // 默认使用等距离空白间隔,空白间隔为图形宽度
+ unitOffset = dvbWidth / (fvc + 1);
+ xBlank = [unitOffset, unitOffset, unitOffset];
+ }
+
+ // 图形 x 轴上的位置计算
+ var xOffset = 0;
+ for (var i = 0; i < fvc; i++) {
+ if (i === 0) {
+ xOffset = xBlank[0];
+ } else {
+ xOffset += unitOffset;
+ }
+
+ xShapePositions.push(dvb[0] + xOffset);
+ }
+
+ return {
+ "xPositions": xShapePositions,
+ "width": xShapeWidth
+ };
+ }
+}
+
+export {Line};
+Zondy.Theme.Line = Line;
\ No newline at end of file
diff --git a/src/common/overlay/Pie.js b/src/common/overlay/Pie.js
new file mode 100644
index 000000000..da30d3b1b
--- /dev/null
+++ b/src/common/overlay/Pie.js
@@ -0,0 +1,207 @@
+import {Zondy} from '../../service/common/Base';
+import {ShapeFactory} from './feature/ShapeFactory';
+import {Sector} from './feature/Sector';
+import {Graph} from './Graph';
+
+/**
+ * @private
+ * @class Zondy.Theme.Pie
+ * @classdesc 饼图。
+ * @param {Zondy.Feature.Vector} data - 用户数据。
+ * @param {Zondy.Layer.Graph} layer - 此专题要素所在图层。
+ * @param {Array.} fields - data 中的参与此图表生成的字段名称。
+ * @param {Zondy.Feature.Theme.Point.setting} setting - 图表配置对象。
+ * @param {Zondy.LonLat} [lonlat] - 专题要素地理位置。默认为 data 指代的地理要素 Bounds 中心。
+ * @extends Zondy.Feature.Theme.Graph
+ *
+ * @typedef {Object} Zondy.Theme.Pie.setting
+ * @property {number} width - 专题要素(图表)宽度。
+ * @property {number} height - 专题要素(图表)高度。
+ * @property {Array.} codomain - 图表允许展示的数据值域,长度为 2 的一维数组,第一个元素表示值域下限,第二个元素表示值域上限。
+ * @property {number} [XOffset] - 专题要素(图表)在 X 方向上的偏移值,单位像素。
+ * @property {number} [YOffset] - 专题要素(图表)在 Y 方向上的偏移值,单位像素。
+ * @property {Array.} [dataViewBoxParameter=[0, 0, 0, 0]] - 数据视图框 dataViewBox 参数,
+ * 它是指图表框 chartBox (由图表位置、图表宽度、图表高度构成的图表范围框)在左、下,右,上四个方向上的内偏距值。
+ * @property {Array.} decimalNumber - 数据值数组 dataValues 元素值小数位数,数据的小数位处理参数,取值范围:[0, 16]。如果不设置此参数,在取数据值时不对数据做小数位处理。
+ * @property {boolean} [useBackground=false] - 是否使用图表背景框。
+ * @property {Zondy.Feature.ShapeParameters.Rectangle.style} backgroundStyle - 背景样式。
+ * @property {Array.} [backgroundRadius=[0, 0, 0, 0]] - 背景框矩形圆角半径,可以用数组分别指定四个角的圆角半径,设:左上、右上、右下、左下角的半径依次为 r1、r2、r3、r4 ,则 backgroundRadius 为 [r1、r2、r3、r4 ]。
+ * @property {Zondy.Feature.ShapeParameters.Sector.style} sectorStyle - 饼图中扇形的基础 style,此参数控制饼图扇形基础样式,优先级低于 sectorStyleByFields 和 sectorStyleByCodomain。
+ * @property {Array.} sectorStyleByFields - 按专题字段 themeFields()为饼图扇形赋 style,此参数按字段控制饼图扇形样式,优先级低于 sectorStyleByCodomain,高于 sectorStyle。此参数中的 style 与 themeFields 中的字段一一对应 。例如: themeFields() 为 ["POP_1992", "POP_1995", "POP_1999"],
+ * sectorStyleByFields 为[style1, style2, style3],则在图表中,字段 POP_1992 对应的饼图扇形使用 style1,字段 POP_1995 对应的饼图扇形使用 style2 ,字段 POP_1999 对应的饼图扇形使用 style3。
+ * @property {Array.} sectorStyleByCodomain - 按饼图扇形代表的数据值所在值域范围控制饼图扇形样式,优先级高于 sectorStyle 和 sectorStyleByFields。
+ * @property {Object} [sectorHoverStyle] 饼图扇形 hover 状态时的样式,sectorHoverAble 为 true 时有效。
+ * @property {boolean} [sectorHoverAble=true] 是否允许饼图扇形使用 hover 状态。同时设置 sectorHoverAble 和 sectorClickAble 为 false,可以直接屏蔽饼图扇形对专题图层事件的响应。
+ * @property {boolean} [sectorClickAble=true] 是否允许饼图扇形被点击。同时设置 sectorHoverAble 和 sectorClickAble 为 false,可以直接屏蔽饼图扇形对专题图层事件的响应。
+ *
+ * @example
+ * // sectorStyleByCodomain 的每个元素是个包含值域信息和与值域对应样式信息的对象,该对象(必须)有三个属性:
+ * // start: 值域值下限(包含);
+ * // end: 值域值上限(不包含);
+ * // style: 数据可视化图形的 style,这个样式对象的可设属性: 。
+ * // sectorStyleByCodomain 数组形如:
+ * [
+ * {
+ * start:0,
+ * end:250,
+ * style:{
+ * fillColor:"#00CD00"
+ * }
+ * },
+ * {
+ * start:250,
+ * end:500,
+ * style:{
+ * fillColor:"#00EE00"
+ * }
+ * },
+ * {
+ * start:500,
+ * end:750,
+ * style:{
+ * fillColor:"#00FF7F"
+ * }
+ * },
+ * {
+ * start:750,
+ * end:1500,
+ * style:{
+ * fillColor:"#00FF00"
+ * }
+ * }
+ * ]
+ * @extends {Zondy.Feature.Theme.Graph}
+ */
+class Pie extends Graph {
+
+ constructor(data, layer, fields, setting, lonlat, option) {
+ super(data, layer, fields, setting, lonlat, option);
+ this.CLASS_NAME = "Zondy.Theme.Pie";
+ }
+
+ /**
+ * @function Zondy.Theme.Pie.prototype.destroy
+ * @description 销毁此专题要素。调用 destroy 后此对象所以属性置为 null。
+ */
+ destroy() {
+ super.destroy();
+ }
+
+ /**
+ * @function Zondy.Theme.Pie.prototype.assembleShapes
+ * @description 装配图形(扩展接口)。
+ */
+ assembleShapes() {
+ // 图表配置对象
+ var sets = this.setting;
+
+ // 一个默认 style 组
+ var defaultStyleGroup = [
+ {fillColor: "#ff9277"}, {fillColor: "#dddd00"}, {fillColor: "#ffc877"}, {fillColor: "#bbe3ff"}, {fillColor: "#d5ffbb"},
+ {fillColor: "#bbbbff"}, {fillColor: "#ddb000"}, {fillColor: "#b0dd00"}, {fillColor: "#e2bbff"}, {fillColor: "#ffbbe3"},
+ {fillColor: "#ff7777"}, {fillColor: "#ff9900"}, {fillColor: "#83dd00"}, {fillColor: "#77e3ff"}, {fillColor: "#778fff"},
+ {fillColor: "#c877ff"}, {fillColor: "#ff77ab"}, {fillColor: "#ff6600"}, {fillColor: "#aa8800"}, {fillColor: "#77c7ff"},
+ {fillColor: "#ad77ff"}, {fillColor: "#ff77ff"}, {fillColor: "#dd0083"}, {fillColor: "#777700"}, {fillColor: "#00aa00"},
+ {fillColor: "#0088aa"}, {fillColor: "#8400dd"}, {fillColor: "#aa0088"}, {fillColor: "#dd0000"}, {fillColor: "#772e00"}
+ ];
+
+ // 重要步骤:初始化参数
+ if (!this.initBaseParameter()) {
+ return;
+ }
+
+ // 背景框,默认不启用
+ if (sets.useBackground) {
+ this.shapes.push(ShapeFactory.Background(this.shapeFactory, this.chartBox, sets));
+ }
+
+ // 数据值数组
+ var fv = this.dataValues;
+ if (fv.length < 1) {
+ return;
+ } // 没有数据
+
+ // 值域范围
+ var codomain = this.DVBCodomain;
+ // 值域范围检测
+ for (let i = 0; i < fv.length; i++) {
+ if (fv[i] < codomain[0] || fv[i] > codomain[1]) {
+ return;
+ }
+ }
+
+ // 值的绝对值总和
+ var valueSum = 0;
+ for (let i = 0; i < fv.length; i++) {
+ valueSum += Math.abs(fv[i]);
+ }
+
+ // 重要步骤:定义图表 Pie 数据视图框中单位值的含义,单位值:每度代表的数值
+ this.DVBUnitValue = 360 / valueSum;
+ var uv = this.DVBUnitValue;
+
+ var dvbCenter = this.DVBCenterPoint; // 数据视图框中心作为扇心
+
+ var startAngle = 0; // 扇形起始边角度
+ var endAngle = 0; // 扇形终止边角度
+ var startAngleTmp = startAngle; // 扇形临时起始边角度
+ // 扇形(自适应)半径
+ var r = this.DVBHeight < this.DVBWidth ? this.DVBHeight / 2 : this.DVBWidth / 2;
+
+ for (var i = 0; i < fv.length; i++) {
+ var fvi = Math.abs(fv[i]);
+ //计算终止角
+ if (i === 0) {
+ endAngle = startAngle + fvi * uv;
+ } else if (i === fvi.length - 1) {
+ endAngle = startAngleTmp;
+ } else {
+ endAngle = startAngle + fvi * uv;
+ }
+ //矫正误差计算
+ if ((endAngle - startAngle) >= 360) {
+ endAngle = 359.9999999;
+ }
+
+ // 扇形参数对象
+ var sectorSP = new Sector(dvbCenter[0], dvbCenter[1], r, startAngle, endAngle);
+ // 扇形样式
+ if (typeof (sets.sectorStyleByFields) === "undefined") {
+ // 使用默认 style 组
+ var colorIndex = i % defaultStyleGroup.length;
+ sectorSP.style = ShapeFactory.ShapeStyleTool(null, sets.sectorStyle, defaultStyleGroup, null, colorIndex);
+ } else {
+ sectorSP.style = ShapeFactory.ShapeStyleTool(null, sets.sectorStyle, sets.sectorStyleByFields, sets.sectorStyleByCodomain, i, fv[i]);
+ }
+
+ // 扇形 hover 样式
+ sectorSP.highlightStyle = ShapeFactory.ShapeStyleTool(null, sets.sectorHoverStyle);
+ // 扇形 hover 与 click 设置
+ if (typeof (sets.sectorHoverAble) !== "undefined") {
+ sectorSP.hoverable = sets.sectorHoverAble;
+ }
+ if (typeof (sets.sectorClickAble) !== "undefined") {
+ sectorSP.clickable = sets.sectorClickAble;
+ }
+ // 图形携带的数据信息
+ sectorSP.refDataID = this.data.FID;
+ sectorSP.dataInfo = {
+ field: this.fields[i],
+ value: fv[i]
+ };
+
+ // 创建扇形并把此扇形添加到图表图形数组
+ this.shapes.push(this.shapeFactory.createShape(sectorSP));
+
+ // 把上一次的结束角度作为下一次的起始角度
+ startAngle = endAngle;
+ }
+
+ // 重要步骤:将图形转为由相对坐标表示的图形,以便在地图平移缩放过程中快速重绘图形
+ // (统计专题图模块从结构上要求使用相对坐标,assembleShapes() 函数必须在图形装配完成后调用 shapesConvertToRelativeCoordinate() 函数)
+ this.shapesConvertToRelativeCoordinate();
+ }
+}
+
+export {Pie};
+Zondy.Theme.Pie = Pie;
\ No newline at end of file
diff --git a/src/common/overlay/Point.js b/src/common/overlay/Point.js
new file mode 100644
index 000000000..b48f492d4
--- /dev/null
+++ b/src/common/overlay/Point.js
@@ -0,0 +1,268 @@
+import {Zondy} from '../../service/common/Base';
+import {ShapeFactory} from './feature/ShapeFactory';
+import {Point as FeaturePoint} from './feature/Point';
+import {Graph} from './Graph';
+
+/**
+ * @private
+ * @class Zondy.Theme.Point
+ * @classdesc 点状图。
+ * @param {Zondy.Feature.Vector} data - 用户数据。
+ * @param {Zondy.Layer.Graph} layer - 此专题要素所在图层。
+ * @param {Array.} fields - data 中的参与此图表生成的字段名称。
+ * @param {Zondy.Theme.Point.setting} setting - 图表配置对象。
+ * @param {Zondy.LonLat} [lonlat] - 专题要素地理位置。默认为 data 指代的地理要素 Bounds 中心。
+ *
+ * @typedef {Object} Zondy.Theme.Point.setting
+ * @property {number} width - 专题要素(图表)宽度。
+ * @property {number} height - 专题要素(图表)高度。
+ * @property {Array.} codomain - 图表允许展示的数据值域,长度为 2 的一维数组,第一个元素表示值域下限,第二个元素表示值域上限。
+ * @property {number} [XOffset] - 专题要素(图表)在 X 方向上的偏移值,单位像素。
+ * @property {number} [YOffset] - 专题要素(图表)在 Y 方向上的偏移值,单位像素。
+ * @property {Array.} [dataViewBoxParameter] - 数据视图框 dataViewBox 参数,
+ * 它是指图表框 chartBox (由图表位置、图表宽度、图表高度构成的图表范围框)在左、下,右,上四个方向上的内偏距值。
+ * 当使用坐标轴时 dataViewBoxParameter 的默认值为:[45, 15, 15, 15];不使用坐标轴时 dataViewBoxParameter 的默认值为:[5, 5, 5, 5]。
+ * @property {number} [decimalNumber] - 数据值数组 dataValues 元素值小数位数,数据的小数位处理参数,取值范围:[0, 16]。如果不设置此参数,在取数据值时不对数据做小数位处理。
+ * @property {boolean} [useBackground] - 是否使用图表背景框。
+ * @property {Zondy.Feature.ShapeParameters.Rectangle.style} backgroundStyle - 背景样式。
+ * @property {Array.} [backgroundRadius=[0, 0, 0, 0]] - 背景框矩形圆角半径,可以用数组分别指定四个角的圆角半径,设:左上、右上、右下、左下角的半径依次为 r1、r2、r3、r4 ,
+ * 则 backgroundRadius 为 [r1、r2、r3、r4 ]。
+ * @property {Array.} xShapeBlank - 水平方向上的图形空白间隔参数。
+ * 长度为 2 的数组,第一个元素表示第一个(沿 x 轴方向)图形点与数据视图框左端的空白间距,第二个元素表示最后一个(沿 x 轴方向)图形点与数据视图框右端端的空白间距 。
+ * @property {Object} axisStyle - 坐标轴样式。
+ * @property {boolean} [axisUseArrow=false] - 坐标轴是否使用箭头。
+ * @property {number} [axisYTick=0] - y 轴刻度数量。
+ * @property {Array.} [axisYLabels] - y 轴上的标签组内容,标签顺序沿着数据视图框左面条边自上而下,等距排布。例如:["1000", "750", "500", "250", "0"]。
+ * @property {Zondy.Feature.ShapeParameters.Label.style} [axisYLabelsStyle] - y 轴上的标签组样式。
+ * @property {Array.} [axisYLabelsOffset=0] - y 轴上的标签组偏移量。长度为 2 的数组,数组第一项表示 y 轴标签组横向上的偏移量,向左为正;
+ * 数组第二项表示 y 轴标签组纵向上的偏移量,向下为正。
+ * @property {Array.} [axisXLabels] - x 轴上的标签组内容,标签顺序沿着数据视图框下面条边自左向右排布,例如:["92年", "95年", "99年"]。
+ * 标签排布规则:当标签数量与 xShapeInfo 中的属性 xShapeCenter 数量相同(即标签个数与数据个数相等时), 按照 xShapeCenter 提供的位置排布标签,
+ * 否则沿数据视图框下面条边等距排布标签。
+ * @property {Zondy.Feature.ShapeParameters.Label.style} [axisXLabelsStyle] - x 轴上的标签组样式。
+ * @property {Array.} [axisXLabelsOffset=0] - x 轴上的标签组偏移量。长度为 2 的数组,数组第一项表示 x 轴标签组横向上的偏移量,向左为正,
+ * 数组第二项表示 x 轴标签组纵向上的偏移量,向下为正。
+ * @property {boolean} [useXReferenceLine=true] - 是否使用水平参考线,如果为 true,在 axisYTick 大于 0 时有效,水平参考线是 y 轴刻度在数据视图框里的延伸。
+ * @property {Zondy.Feature.ShapeParameters.Line.style} [xReferenceLineStyle] - 水平参考线样式。
+ * @property {Zondy.Feature.ShapeParameters.Point.style} [pointStyle] - 点状图中图形点基础 style,此参数控制图形点基础样式,优先级低于 pointStyleByFields 和 pointStyleByCodomain。
+ * @property {Array.} [pointStyleByFields] - 按专题字段 themeFields()为图形点赋 style,此参数按字段控制图形点样式,
+ * 优先级低于 pointStyleByCodomain,高于 pointStyle。此数组中的元素是样式对象。
+ * 此参数中的 style 与 themeFields 中的字段一一对应 。例如: themeFields(