This is an automated email from the ASF dual-hosted git repository.

sushuang pushed a commit to branch fix/geo-svg
in repository https://gitbox.apache.org/repos/asf/echarts.git

commit e9af050807d890ed99ddf6ecf8fa891993ca5e22
Author: 100pah <[email protected]>
AuthorDate: Sun Mar 7 01:06:13 2021 +0800

    fix: [geo]
    (1) refactor of geoJSON & SVG source loader and management.
    (2) remove the support of "load multiple geoJSON or SVG in one map name". 
This feature is never documented and published. And this feature have a 
inherent problem that it's not easy to normalize the unit among the different 
SVG. After this commit, one map name can only have one geoJSON or one SVG.
---
 src/component/helper/MapDraw.ts                    |  50 +++--
 src/coord/View.ts                                  |   6 +-
 src/coord/geo/GeoJSONResource.ts                   | 166 ++++++++++++++++
 .../geo/{geoSVGLoader.ts => GeoSVGResource.ts}     | 115 ++++++-----
 src/coord/geo/geoCreator.ts                        |   6 +-
 src/coord/geo/geoJSONLoader.ts                     |  99 ----------
 src/coord/geo/geoSourceManager.ts                  | 217 +++++++++++----------
 src/coord/geo/geoTypes.ts                          |  19 ++
 src/coord/geo/mapDataStorage.ts                    | 158 ---------------
 src/core/echarts.ts                                |  21 +-
 10 files changed, 416 insertions(+), 441 deletions(-)

diff --git a/src/component/helper/MapDraw.ts b/src/component/helper/MapDraw.ts
index e1a8f47..79bc1cc 100644
--- a/src/component/helper/MapDraw.ts
+++ b/src/component/helper/MapDraw.ts
@@ -38,10 +38,12 @@ import Model from '../../model/Model';
 import { setLabelStyle, getLabelStatesModels } from '../../label/labelStyle';
 import { getECData } from '../../util/innerStore';
 import { createOrUpdatePatternFromDecal } from '../../util/decal';
+import { ViewCoordSysTransformInfoPart } from '../../coord/View';
+import { GeoSVGResource } from '../../coord/geo/GeoSVGResource';
 
 
 interface RegionsGroup extends graphic.Group {
-    __regions: Region[];
+    __regions: Region[]
 }
 
 function getFixedItemStyle(model: Model<GeoItemStyleOption>) {
@@ -82,7 +84,7 @@ class MapDraw {
 
     private _regionsGroup: RegionsGroup;
 
-    private _backgroundGroup: graphic.Group;
+    private _svgGroup: graphic.Group;
 
 
     constructor(api: ExtensionAPI) {
@@ -93,7 +95,7 @@ class MapDraw {
         this.group = group;
 
         group.add(this._regionsGroup = new graphic.Group() as RegionsGroup);
-        group.add(this._backgroundGroup = new graphic.Group());
+        group.add(this._svgGroup = new graphic.Group());
     }
 
     draw(
@@ -117,7 +119,6 @@ class MapDraw {
 
         const geo = mapOrGeoModel.coordinateSystem;
 
-        this._updateBackground(geo);
 
         const regionsGroup = this._regionsGroup;
         const group = this.group;
@@ -126,6 +127,8 @@ class MapDraw {
         const transformInfoRaw = transformInfo.raw;
         const transformInfoRoam = transformInfo.roam;
 
+        this._updateSVG(geo, transformInfoRaw);
+
         // No animation when first draw or in action
         const isFirstDraw = !regionsGroup.childAt(0) || payload;
 
@@ -356,23 +359,48 @@ class MapDraw {
 
     remove(): void {
         this._regionsGroup.removeAll();
-        this._backgroundGroup.removeAll();
+        this._svgGroup.removeAll();
         this._controller.dispose();
-        this._mapName && geoSourceManager.removeGraphic(this._mapName, 
this.uid);
+        this._freeSVG(this._mapName);
         this._mapName = null;
         this._controllerHost = null;
     }
 
-    private _updateBackground(geo: Geo): void {
+    private _updateSVG(geo: Geo, transformInfoRaw: 
ViewCoordSysTransformInfoPart): void {
         const mapName = geo.map;
 
+        this._svgGroup.x = transformInfoRaw.x;
+        this._svgGroup.y = transformInfoRaw.y;
+        this._svgGroup.scaleX = transformInfoRaw.scaleX;
+        this._svgGroup.scaleY = transformInfoRaw.scaleY;
+
         if (this._mapName !== mapName) {
-            zrUtil.each(geoSourceManager.makeGraphic(mapName, this.uid), 
function (root) {
-                this._backgroundGroup.add(root);
-            }, this);
+            this._freeSVG(this._mapName);
+            this._useSVG(mapName);
+            this._mapName = mapName;
         }
+    }
 
-        this._mapName = mapName;
+    private _useSVG(mapName: string) {
+        if (mapName == null) {
+            return;
+        }
+        const resource = geoSourceManager.getGeoResource(mapName);
+        if (resource && resource.type === 'svg') {
+            const root = (resource as GeoSVGResource).useGraphic(this.uid);
+            this._svgGroup.add(root);
+        }
+    }
+
+    private _freeSVG(mapName: string) {
+        if (mapName == null) {
+            return;
+        }
+        const resource = geoSourceManager.getGeoResource(mapName);
+        if (resource && resource.type === 'svg') {
+            (resource as GeoSVGResource).freeGraphic(this.uid);
+            this._svgGroup.removeAll();
+        }
     }
 
     private _updateController(
diff --git a/src/coord/View.ts b/src/coord/View.ts
index 6a72a3f..93fa411 100644
--- a/src/coord/View.ts
+++ b/src/coord/View.ts
@@ -32,6 +32,8 @@ import { ParsedModelFinder, ParsedModelFinderKnown } from 
'../util/model';
 
 const v2ApplyTransform = vector.applyTransform;
 
+export type ViewCoordSysTransformInfoPart = Pick<Transformable, 'x' | 'y' | 
'scaleX' | 'scaleY'>;
+
 class View extends Transformable implements CoordinateSystemMaster, 
CoordinateSystem {
 
     readonly type: string = 'view';
@@ -219,8 +221,8 @@ class View extends Transformable implements 
CoordinateSystemMaster, CoordinateSy
     }
 
     getTransformInfo(): {
-        roam: Pick<Transformable, 'x' | 'y' | 'scaleX' | 'scaleY'>
-        raw: Pick<Transformable, 'x' | 'y' | 'scaleX' | 'scaleY'>
+        roam: ViewCoordSysTransformInfoPart
+        raw: ViewCoordSysTransformInfoPart
     } {
         const roamTransformable = this._roamTransformable;
         const rawTransformable = this._rawTransformable;
diff --git a/src/coord/geo/GeoJSONResource.ts b/src/coord/geo/GeoJSONResource.ts
new file mode 100644
index 0000000..6189322
--- /dev/null
+++ b/src/coord/geo/GeoJSONResource.ts
@@ -0,0 +1,166 @@
+/*
+* Licensed to the Apache Software Foundation (ASF) under one
+* or more contributor license agreements.  See the NOTICE file
+* distributed with this work for additional information
+* regarding copyright ownership.  The ASF licenses this file
+* to you under the Apache License, Version 2.0 (the
+* "License"); you may not use this file except in compliance
+* with the License.  You may obtain a copy of the License at
+*
+*   http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing,
+* software distributed under the License is distributed on an
+* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+* KIND, either express or implied.  See the License for the
+* specific language governing permissions and limitations
+* under the License.
+*/
+
+
+import { each, isString, createHashMap } from 'zrender/src/core/util';
+import parseGeoJson from './parseGeoJson';
+// Built-in GEO fixer.
+import fixNanhai from './fix/nanhai';
+import fixTextCoord from './fix/textCoord';
+import fixGeoCoord from './fix/geoCoord';
+import fixDiaoyuIsland from './fix/diaoyuIsland';
+import BoundingRect from 'zrender/src/core/BoundingRect';
+import Region from './Region';
+import { GeoJSON, GeoJSONCompressed, GeoJSONSourceInput, GeoResource, 
GeoSpecialAreas, NameMap } from './geoTypes';
+
+
+export class GeoJSONResource implements GeoResource {
+
+    readonly type = 'geoJSON';
+    private _geoJSON: GeoJSON | GeoJSONCompressed;
+    private _specialAreas: GeoSpecialAreas;
+    private _mapName: string;
+
+    private _parsed: {
+        regions: Region[];
+        boundingRect: BoundingRect;
+    };
+
+    constructor(
+        mapName: string,
+        geoJSON: GeoJSONSourceInput,
+        specialAreas: GeoSpecialAreas
+    ) {
+        this._mapName = mapName;
+        this._specialAreas = specialAreas;
+
+        // PENDING: delay the parse to the first usage to rapid up the FMP?
+        this._geoJSON = parseInput(geoJSON);
+    }
+
+    load(nameMap: NameMap, nameProperty: string) {
+
+        let parsed = this._parsed;
+        if (!parsed) {
+            const rawRegions = this._parseToRegions(nameProperty);
+            parsed = this._parsed = {
+                regions: rawRegions,
+                boundingRect: calculateBoundingRect(rawRegions)
+            };
+        }
+
+        const regionsMap = createHashMap<Region>();
+        const nameCoordMap = createHashMap<Region['center']>();
+
+        const finalRegions: Region[] = [];
+        each(parsed.regions, function (region) {
+            let regionName = region.name;
+
+            // Try use the alias in geoNameMap
+            if (nameMap && nameMap.hasOwnProperty(regionName)) {
+                region = region.cloneShallow(regionName = nameMap[regionName]);
+            }
+
+            finalRegions.push(region);
+            regionsMap.set(regionName, region);
+            nameCoordMap.set(regionName, region.center);
+        });
+
+        return {
+            regions: finalRegions,
+            boundingRect: parsed.boundingRect || new BoundingRect(0, 0, 0, 0),
+            regionsMap: regionsMap,
+            nameCoordMap: nameCoordMap
+        };
+    }
+
+    private _parseToRegions(nameProperty: string): Region[] {
+        const mapName = this._mapName;
+        const geoJSON = this._geoJSON;
+        let rawRegions;
+
+        // https://jsperf.com/try-catch-performance-overhead
+        try {
+            rawRegions = geoJSON ? parseGeoJson(geoJSON, nameProperty) : [];
+        }
+        catch (e) {
+            throw new Error('Invalid geoJson format\n' + e.message);
+        }
+
+        fixNanhai(mapName, rawRegions);
+
+        each(rawRegions, function (region) {
+            const regionName = region.name;
+
+            fixTextCoord(mapName, region);
+            fixGeoCoord(mapName, region);
+            fixDiaoyuIsland(mapName, region);
+
+            // Some area like Alaska in USA map needs to be tansformed
+            // to look better
+            const specialArea = this._specialAreas && 
this._specialAreas[regionName];
+            if (specialArea) {
+                region.transformTo(
+                    specialArea.left, specialArea.top, specialArea.width, 
specialArea.height
+                );
+            }
+        }, this);
+
+        return rawRegions;
+    }
+
+    /**
+     * Only for exporting to users.
+     * **MUST NOT** used internally.
+     */
+    getMapForUser(): {
+        // backward compat.
+        geoJson: GeoJSON | GeoJSONCompressed;
+        geoJSON: GeoJSON | GeoJSONCompressed;
+        specialAreas: GeoSpecialAreas;
+    } {
+        return {
+            // For backward compatibility, use geoJson
+            // PENDING: it has been returning them without clone.
+            // do we need to avoid outsite modification?
+            geoJson: this._geoJSON,
+            geoJSON: this._geoJSON,
+            specialAreas: this._specialAreas
+        };
+    }
+
+}
+
+function calculateBoundingRect(regions: Region[]): BoundingRect {
+    let rect;
+    for (let i = 0; i < regions.length; i++) {
+        const regionRect = regions[i].getBoundingRect();
+        rect = rect || regionRect.clone();
+        rect.union(regionRect);
+    }
+    return rect;
+}
+
+function parseInput(source: GeoJSONSourceInput): GeoJSON | GeoJSONCompressed {
+    return !isString(source)
+        ? source
+        : (typeof JSON !== 'undefined' && JSON.parse)
+        ? JSON.parse(source)
+        : (new Function('return (' + source + ');'))();
+}
diff --git a/src/coord/geo/geoSVGLoader.ts b/src/coord/geo/GeoSVGResource.ts
similarity index 50%
rename from src/coord/geo/geoSVGLoader.ts
rename to src/coord/geo/GeoSVGResource.ts
index 349dbb7..14a29c3 100644
--- a/src/coord/geo/geoSVGLoader.ts
+++ b/src/coord/geo/GeoSVGResource.ts
@@ -22,82 +22,95 @@ import Group from 'zrender/src/graphic/Group';
 import Rect from 'zrender/src/graphic/shape/Rect';
 import {assert, createHashMap, HashMap} from 'zrender/src/core/util';
 import BoundingRect from 'zrender/src/core/BoundingRect';
-import {makeInner} from '../../util/model';
-import { SVGMapRecord } from './mapDataStorage';
+import { GeoResource, GeoSVGSourceInput } from './geoTypes';
+import { parseXML } from 'zrender/src/tool/parseXML';
 
-type MapRecordInner = {
-    originRoot: Group;
-    boundingRect: BoundingRect;
-    // key: hostKey, value: root
-    rootMap: HashMap<Group>;
-    originRootHostKey: string;
-};
 
-const inner = makeInner<MapRecordInner, SVGMapRecord>();
+export class GeoSVGResource implements GeoResource {
 
-export default {
+    readonly type = 'svg';
+    private _mapName: string;
+    private _parsedXML: SVGElement;
+    private _rootForRect: Group;
+    private _boundingRect: BoundingRect;
+    // key: hostKey, value: root
+    private _usedRootMap: HashMap<Group> = createHashMap();
+    private _freedRoots: Group[] = [];
+
+    constructor(
+        mapName: string,
+        svg: GeoSVGSourceInput
+    ) {
+        this._mapName = mapName;
+
+        // Only perform parse to XML object here, which might be time
+        // consiming for large SVG.
+        // Although convert XML to zrender element is also time consiming,
+        // if we do it here, the clone of zrender elements has to be
+        // required. So we do it once for each geo instance, util real
+        // performance issues call for optimizing it.
+        this._parsedXML = parseXML(svg);
+    }
 
-    load(mapName: string, mapRecord: SVGMapRecord): ReturnType<typeof 
buildGraphic> {
-        const originRoot = inner(mapRecord).originRoot;
-        if (originRoot) {
-            return {
-                root: originRoot,
-                boundingRect: inner(mapRecord).boundingRect
-            };
+    load(): { boundingRect: BoundingRect } {
+        // In the "load" stage, graphic need to be built to
+        // get boundingRect for geo coordinate system.
+        const rootForRect = this._rootForRect;
+        if (rootForRect) {
+            return { boundingRect: this._boundingRect };
         }
 
-        const graphic = buildGraphic(mapRecord);
+        const graphic = buildGraphic(this._parsedXML);
 
-        inner(mapRecord).originRoot = graphic.root;
-        inner(mapRecord).boundingRect = graphic.boundingRect;
+        this._rootForRect = graphic.root;
+        this._boundingRect = graphic.boundingRect;
 
-        return graphic;
-    },
+        this._freedRoots.push(graphic.root);
 
-    makeGraphic(mapName: string, mapRecord: SVGMapRecord, hostKey: string): 
Group {
-        // For performance consideration (in large SVG), graphic only maked
-        // when necessary and reuse them according to hostKey.
-        const field = inner(mapRecord);
-        const rootMap = field.rootMap || (field.rootMap = createHashMap());
+        return { boundingRect: graphic.boundingRect };
+    }
 
-        let root = rootMap.get(hostKey);
+    // Consider:
+    // (1) One graphic element can not be shared by different `geoView` 
running simultaneously.
+    //     Notice, also need to consider multiple echarts instances share a 
`mapRecord`.
+    // (2) Converting SVG to graphic elements is time consuming.
+    // (3) In the current architecture, `load` should be called frequently to 
get boundingRect,
+    //     and it is called without view info.
+    // So we maintain graphic elements in this module, and enables `view` to 
use/return these
+    // graphics from/to the pool with it's uid.
+    useGraphic(hostKey: string): Group {
+        const usedRootMap = this._usedRootMap;
+
+        let root = usedRootMap.get(hostKey);
         if (root) {
             return root;
         }
 
-        const originRoot = field.originRoot;
-        const boundingRect = field.boundingRect;
+        root = this._freedRoots.pop() || buildGraphic(this._parsedXML, 
this._boundingRect).root;
 
-        // For performance, if originRoot is not used by a view,
-        // assign it to a view, but not reproduce graphic elements.
-        if (!field.originRootHostKey) {
-            field.originRootHostKey = hostKey;
-            root = originRoot;
-        }
-        else {
-            root = buildGraphic(mapRecord, boundingRect).root;
-        }
+        return usedRootMap.set(hostKey, root);
+    }
 
-        return rootMap.set(hostKey, root);
-    },
+    freeGraphic(hostKey: string): void {
+        const usedRootMap = this._usedRootMap;
 
-    removeGraphic(mapName: string, mapRecord: SVGMapRecord, hostKey: string): 
void {
-        const field = inner(mapRecord);
-        const rootMap = field.rootMap;
-        rootMap && rootMap.removeKey(hostKey);
-        if (hostKey === field.originRootHostKey) {
-            field.originRootHostKey = null;
+        const root = usedRootMap.get(hostKey);
+        if (root) {
+            usedRootMap.removeKey(hostKey);
+            this._freedRoots.push(root);
         }
     }
-};
+
+}
+
 
 function buildGraphic(
-    mapRecord: SVGMapRecord, boundingRect?: BoundingRect
+    svgXML: SVGElement,
+    boundingRect?: BoundingRect
 ): {
     root: Group;
     boundingRect: BoundingRect;
 } {
-    const svgXML = mapRecord.svgXML;
     let result;
     let root;
 
diff --git a/src/coord/geo/geoCreator.ts b/src/coord/geo/geoCreator.ts
index fad9400..e01f0d8 100644
--- a/src/coord/geo/geoCreator.ts
+++ b/src/coord/geo/geoCreator.ts
@@ -22,7 +22,6 @@ import Geo from './Geo';
 import * as layout from '../../util/layout';
 import * as numberUtil from '../../util/number';
 import geoSourceManager from './geoSourceManager';
-import mapDataStorage from './mapDataStorage';
 import GeoModel, { GeoOption, RegoinOption } from './GeoModel';
 import MapSeries, { MapSeriesOption } from '../../chart/map/MapSeries';
 import ExtensionAPI from '../../core/ExtensionAPI';
@@ -141,8 +140,9 @@ class GeoCreator implements CoordinateSystemCreator {
 
             let aspectScale = geoModel.get('aspectScale');
             let invertLongitute = true;
-            const mapRecords = mapDataStorage.retrieveMap(name);
-            if (mapRecords && mapRecords[0] && mapRecords[0].type === 'svg') {
+
+            const geoResource = geoSourceManager.getGeoResource(name);
+            if (geoResource.type === 'svg') {
                 aspectScale == null && (aspectScale = 1);
                 invertLongitute = false;
             }
diff --git a/src/coord/geo/geoJSONLoader.ts b/src/coord/geo/geoJSONLoader.ts
deleted file mode 100644
index 93e5c9e..0000000
--- a/src/coord/geo/geoJSONLoader.ts
+++ /dev/null
@@ -1,99 +0,0 @@
-/*
-* Licensed to the Apache Software Foundation (ASF) under one
-* or more contributor license agreements.  See the NOTICE file
-* distributed with this work for additional information
-* regarding copyright ownership.  The ASF licenses this file
-* to you under the Apache License, Version 2.0 (the
-* "License"); you may not use this file except in compliance
-* with the License.  You may obtain a copy of the License at
-*
-*   http://www.apache.org/licenses/LICENSE-2.0
-*
-* Unless required by applicable law or agreed to in writing,
-* software distributed under the License is distributed on an
-* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
-* KIND, either express or implied.  See the License for the
-* specific language governing permissions and limitations
-* under the License.
-*/
-
-import {each} from 'zrender/src/core/util';
-import parseGeoJson from './parseGeoJson';
-import {makeInner} from '../../util/model';
-
-// Built-in GEO fixer.
-import fixNanhai from './fix/nanhai';
-import fixTextCoord from './fix/textCoord';
-import fixGeoCoord from './fix/geoCoord';
-import fixDiaoyuIsland from './fix/diaoyuIsland';
-import { GeoJSONMapRecord } from './mapDataStorage';
-import BoundingRect from 'zrender/src/core/BoundingRect';
-import Region from './Region';
-
-type MapRecordInner = {
-    parsed: {
-        regions: Region[];
-        boundingRect: BoundingRect;
-    };
-};
-
-const inner = makeInner<MapRecordInner, GeoJSONMapRecord>();
-
-export default {
-
-    load(mapName: string, mapRecord: GeoJSONMapRecord, nameProperty: string): 
MapRecordInner['parsed'] {
-
-        const parsed = inner(mapRecord).parsed;
-
-        if (parsed) {
-            return parsed;
-        }
-
-        const specialAreas = mapRecord.specialAreas || {};
-        const geoJSON = mapRecord.geoJSON;
-        let regions;
-
-        // https://jsperf.com/try-catch-performance-overhead
-        try {
-            regions = geoJSON ? parseGeoJson(geoJSON, nameProperty) : [];
-        }
-        catch (e) {
-            throw new Error('Invalid geoJson format\n' + e.message);
-        }
-
-        fixNanhai(mapName, regions);
-
-        each(regions, function (region) {
-            const regionName = region.name;
-
-            fixTextCoord(mapName, region);
-            fixGeoCoord(mapName, region);
-            fixDiaoyuIsland(mapName, region);
-
-            // Some area like Alaska in USA map needs to be tansformed
-            // to look better
-            const specialArea = specialAreas[regionName];
-            if (specialArea) {
-                region.transformTo(
-                    specialArea.left, specialArea.top, specialArea.width, 
specialArea.height
-                );
-            }
-        });
-
-        return (inner(mapRecord).parsed = {
-            regions: regions,
-            boundingRect: getBoundingRect(regions)
-        });
-    }
-};
-
-function getBoundingRect(regions: Region[]): BoundingRect {
-    let rect;
-    for (let i = 0; i < regions.length; i++) {
-        const regionRect = regions[i].getBoundingRect();
-        rect = rect || regionRect.clone();
-        rect.union(regionRect);
-    }
-    return rect;
-}
-
diff --git a/src/coord/geo/geoSourceManager.ts 
b/src/coord/geo/geoSourceManager.ts
index e324bbf..66cdf1e 100644
--- a/src/coord/geo/geoSourceManager.ts
+++ b/src/coord/geo/geoSourceManager.ts
@@ -17,122 +17,133 @@
 * under the License.
 */
 
-import {each, createHashMap, HashMap} from 'zrender/src/core/util';
-import mapDataStorage, { MapRecord } from './mapDataStorage';
-import geoJSONLoader from './geoJSONLoader';
-import geoSVGLoader from './geoSVGLoader';
-import BoundingRect from 'zrender/src/core/BoundingRect';
-import { NameMap } from './geoTypes';
-import Region from './Region';
-import { Dictionary } from 'zrender/src/core/types';
-import Group from 'zrender/src/graphic/Group';
-
-
-interface Loader {
-    load: (mapName: string, mapRecord: MapRecord, nameProperty?: string) => {
-        regions?: Region[];
-        boundingRect?: BoundingRect;
-    };
-    makeGraphic?: (mapName: string, mapRecord: MapRecord, hostKey: string) => 
Group;
-    removeGraphic?: (mapName: string, mapRecord: MapRecord, hostKey: string) 
=> void;
+import { createHashMap } from 'zrender/src/core/util';
+import { GeoSVGResource } from './GeoSVGResource';
+import {
+    GeoJSON,
+    GeoJSONSourceInput,
+    GeoResource,
+    GeoSpecialAreas,
+    NameMap,
+    GeoSVGSourceInput
+} from './geoTypes';
+import { GeoJSONResource } from './GeoJSONResource';
+
+
+type MapInput = GeoJSONMapInput | SVGMapInput;
+interface GeoJSONMapInput {
+    geoJSON: GeoJSONSourceInput;
+    specialAreas: GeoSpecialAreas;
 }
-const loaders = {
-    geoJSON: geoJSONLoader,
-    svg: geoSVGLoader
-} as Dictionary<Loader>;
+interface GeoJSONMapInputCompat extends GeoJSONMapInput {
+    geoJson: GeoJSONSourceInput;
+}
+interface SVGMapInput {
+    svg: GeoSVGSourceInput;
+}
+
+
+const storage = createHashMap<GeoResource>();
+
 
 export default {
 
-    load: function (mapName: string, nameMap: NameMap, nameProperty?: string): 
{
-        regions: Region[];
-        // Key: mapName
-        regionsMap: HashMap<Region>;
-        // Key: mapName
-        nameCoordMap: HashMap<number[]>;
-        boundingRect: BoundingRect
-    } {
-        const regions = [] as Region[];
-        const regionsMap = createHashMap<Region>();
-        const nameCoordMap = createHashMap<Region['center']>();
-        let boundingRect: BoundingRect;
-        const mapRecords = retrieveMap(mapName);
-
-        each(mapRecords, function (record) {
-            const singleSource = loaders[record.type].load(mapName, record, 
nameProperty);
-
-            each(singleSource.regions, function (region) {
-                let regionName = region.name;
-
-                // Try use the alias in geoNameMap
-                if (nameMap && nameMap.hasOwnProperty(regionName)) {
-                    region = region.cloneShallow(regionName = 
nameMap[regionName]);
-                }
-
-                regions.push(region);
-                regionsMap.set(regionName, region);
-                nameCoordMap.set(regionName, region.center);
-            });
-
-            const rect = singleSource.boundingRect;
-            if (rect) {
-                boundingRect
-                    ? boundingRect.union(rect)
-                    : (boundingRect = rect.clone());
+    /**
+     * Compatible with previous `echarts.registerMap`.
+     *
+     * @usage
+     * ```js
+     *
+     * echarts.registerMap('USA', geoJson, specialAreas);
+     *
+     * echarts.registerMap('USA', {
+     *     geoJson: geoJson,
+     *     specialAreas: {...}
+     * });
+     * echarts.registerMap('USA', {
+     *     geoJSON: geoJson,
+     *     specialAreas: {...}
+     * });
+     *
+     * echarts.registerMap('airport', {
+     *     svg: svg
+     * }
+     * ```
+     *
+     * Note:
+     * Do not support that register multiple geoJSON or SVG
+     * one map name. Because different geoJSON and SVG have
+     * different unit. It's not easy to make sure how those
+     * units are mapping/normalize.
+     * If intending to use multiple geoJSON or SVG, we can
+     * use multiple geo coordinate system.
+     */
+    registerMap: function (
+        mapName: string,
+        rawDef: MapInput | GeoJSONSourceInput,
+        rawSpecialAreas?: GeoSpecialAreas
+    ): void {
+
+        if ((rawDef as SVGMapInput).svg) {
+            const resource = new GeoSVGResource(
+                mapName,
+                (rawDef as SVGMapInput).svg
+            );
+
+            storage.set(mapName, resource);
+        }
+        else {
+            // Recommend:
+            //     echarts.registerMap('eu', { geoJSON: xxx, specialAreas: xxx 
});
+            // Backward compatibility:
+            //     echarts.registerMap('eu', geoJSON, specialAreas);
+            //     echarts.registerMap('eu', { geoJson: xxx, specialAreas: xxx 
});
+            let geoJSON = (rawDef as GeoJSONMapInputCompat).geoJson
+                || (rawDef as GeoJSONMapInput).geoJSON;
+            if (geoJSON && !(rawDef as GeoJSON).features) {
+                rawSpecialAreas = (rawDef as GeoJSONMapInput).specialAreas;
+            }
+            else {
+                geoJSON = rawDef as GeoJSONSourceInput;
             }
-        });
-
-        return {
-            regions: regions,
-            regionsMap: regionsMap,
-            nameCoordMap: nameCoordMap,
-            // FIXME Always return new ?
-            boundingRect: boundingRect || new BoundingRect(0, 0, 0, 0)
-        };
+            const resource = new GeoJSONResource(
+                mapName,
+                geoJSON,
+                rawSpecialAreas
+            );
+
+            storage.set(mapName, resource);
+        }
     },
 
-    /**
-     * @param hostKey For cache.
-     * @return Roots.
-     */
-    makeGraphic: function (mapName: string, hostKey: string): Group[] {
-        const mapRecords = retrieveMap(mapName);
-        const results = [] as Group[];
-        each(mapRecords, function (record) {
-            const method = loaders[record.type].makeGraphic;
-            method && results.push(method(mapName, record, hostKey));
-        });
-        return results;
+    getGeoResource(mapName: string): GeoResource {
+        return storage.get(mapName);
     },
 
     /**
-     * @param hostKey For cache.
+     * Only for exporting to users.
+     * **MUST NOT** used internally.
      */
-    removeGraphic: function (mapName: string, hostKey: string): void {
-        const mapRecords = retrieveMap(mapName);
-        each(mapRecords, function (record) {
-            const method = loaders[record.type].makeGraphic;
-            method && method(mapName, record, hostKey);
-        });
-    }
-};
-
-function mapNotExistsError(mapName: string): void {
-    if (__DEV__) {
-        console.error(
-            'Map ' + mapName + ' not exists. The GeoJSON of the map must be 
provided.'
-        );
-    }
-}
+    getMapForUser: function (mapName: string): 
ReturnType<GeoJSONResource['getMapForUser']> {
+        const resource = storage.get(mapName);
+        // Do not support return SVG until some real requirement come.
+        return resource && resource.type === 'geoJSON'
+            && (resource as GeoJSONResource).getMapForUser();
+    },
 
-function retrieveMap(mapName: string): MapRecord[] {
-    const mapRecords = mapDataStorage.retrieveMap(mapName) || [];
+    load: function (mapName: string, nameMap: NameMap, nameProperty?: string) {
+        const resource = storage.get(mapName);
 
-    if (__DEV__) {
-        if (!mapRecords.length) {
-            mapNotExistsError(mapName);
+        if (!resource) {
+            if (__DEV__) {
+                console.error(
+                    'Map ' + mapName + ' not exists. The GeoJSON of the map 
must be provided.'
+                );
+            }
+            return;
         }
-    }
 
-    return mapRecords;
-}
+        return resource.load(nameMap, nameProperty);
+    }
 
+};
diff --git a/src/coord/geo/geoTypes.ts b/src/coord/geo/geoTypes.ts
index 910e0fe..52b8eb2 100644
--- a/src/coord/geo/geoTypes.ts
+++ b/src/coord/geo/geoTypes.ts
@@ -17,6 +17,13 @@
 * under the License.
 */
 
+import BoundingRect from 'zrender/src/core/BoundingRect';
+import { HashMap } from 'zrender/src/core/util';
+import Region from './Region';
+
+
+export type GeoSVGSourceInput = 'string' | Document | SVGElement;
+export type GeoJSONSourceInput = 'string' | GeoJSON | GeoJSONCompressed;
 
 export interface NameMap {
     [regionName: string]: string
@@ -114,3 +121,15 @@ interface GeoJSONGeometryMultiPolygonCompressed {
 //      type: 'GeometryCollection';
 //      geometries: GeoJSONGeometry[];
 // };
+
+export interface GeoResource {
+    readonly type: 'geoJSON' | 'svg';
+    load(nameMap: NameMap, nameProperty: string): {
+        boundingRect: BoundingRect;
+        regions?: Region[];
+        // Key: mapName
+        regionsMap?: HashMap<Region>;
+        // Key: mapName
+        nameCoordMap?: HashMap<number[]>;
+    };
+}
diff --git a/src/coord/geo/mapDataStorage.ts b/src/coord/geo/mapDataStorage.ts
deleted file mode 100644
index b9562f9..0000000
--- a/src/coord/geo/mapDataStorage.ts
+++ /dev/null
@@ -1,158 +0,0 @@
-/*
-* Licensed to the Apache Software Foundation (ASF) under one
-* or more contributor license agreements.  See the NOTICE file
-* distributed with this work for additional information
-* regarding copyright ownership.  The ASF licenses this file
-* to you under the Apache License, Version 2.0 (the
-* "License"); you may not use this file except in compliance
-* with the License.  You may obtain a copy of the License at
-*
-*   http://www.apache.org/licenses/LICENSE-2.0
-*
-* Unless required by applicable law or agreed to in writing,
-* software distributed under the License is distributed on an
-* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
-* KIND, either express or implied.  See the License for the
-* specific language governing permissions and limitations
-* under the License.
-*/
-
-import {createHashMap, isString, isArray, each, assert} from 
'zrender/src/core/util';
-import {parseXML} from 'zrender/src/tool/parseXML';
-import { GeoSpecialAreas, GeoJSON, GeoJSONCompressed } from './geoTypes';
-import { Dictionary } from 'zrender/src/core/types';
-
-// For minimize the code size of common echarts package,
-// do not put too much logic in this module.
-
-type SVGMapSource = 'string' | Document | SVGElement;
-type GeoJSONMapSource = 'string' | GeoJSON | GeoJSONCompressed;
-type MapInputObject = {
-    geoJSON?: GeoJSONMapSource;
-    geoJson?: GeoJSONMapSource;
-    svg?: SVGMapSource;
-    specialAreas?: GeoSpecialAreas;
-};
-
-export type MapRecord = GeoJSONMapRecord | SVGMapRecord;
-export interface GeoJSONMapRecord {
-    type: 'geoJSON';
-    source: GeoJSONMapSource;
-    specialAreas: GeoSpecialAreas;
-    geoJSON: GeoJSON | GeoJSONCompressed;
-}
-export interface SVGMapRecord {
-    type: 'svg';
-    source: SVGMapSource;
-    specialAreas: GeoSpecialAreas;
-    svgXML: ReturnType<typeof parseXML>;
-}
-
-
-const storage = createHashMap<MapRecord[]>();
-
-
-export default {
-
-    /**
-     * Compatible with previous `echarts.registerMap`.
-     * @usage
-     * ```js
-     * $.get('USA.json', function (geoJson) {
-     *     echarts.registerMap('USA', geoJson);
-     *     // Or
-     *     echarts.registerMap('USA', {
-     *         geoJson: geoJson,
-     *         specialAreas: {}
-     *     })
-     * });
-     *
-     * $.get('airport.svg', function (svg) {
-     *     echarts.registerMap('airport', {
-     *         svg: svg
-     *     }
-     * });
-     *
-     * echarts.registerMap('eu', [
-     *     {svg: eu-topographic.svg},
-     *     {geoJSON: eu.json}
-     * ])
-     * ```
-     */
-    registerMap: function (
-        mapName: string,
-        rawDef: MapInputObject | MapRecord[] | GeoJSONMapSource,
-        rawSpecialAreas?: GeoSpecialAreas
-    ): MapRecord[] {
-
-        let records: MapRecord[];
-
-        if (isArray(rawDef)) {
-            records = rawDef as MapRecord[];
-        }
-        else if ((rawDef as MapInputObject).svg) {
-            records = [{
-                type: 'svg',
-                source: (rawDef as MapInputObject).svg,
-                specialAreas: (rawDef as MapInputObject).specialAreas
-            } as SVGMapRecord];
-        }
-        else {
-            // Backward compatibility.
-            const geoSource = (rawDef as MapInputObject).geoJson
-                || (rawDef as MapInputObject).geoJSON;
-            if (geoSource && !(rawDef as GeoJSON).features) {
-                rawSpecialAreas = (rawDef as MapInputObject).specialAreas;
-                rawDef = geoSource;
-            }
-            records = [{
-                type: 'geoJSON',
-                source: rawDef as GeoJSONMapSource,
-                specialAreas: rawSpecialAreas
-            } as GeoJSONMapRecord];
-        }
-
-        each(records, function (record) {
-            let type = record.type;
-            (type as any) === 'geoJson' && (type = record.type = 'geoJSON');
-
-            const parse = parsers[type];
-
-            if (__DEV__) {
-                assert(parse, 'Illegal map type: ' + type);
-            }
-
-            parse(record);
-        });
-
-        return storage.set(mapName, records);
-    },
-
-    retrieveMap: function (mapName: string): MapRecord[] {
-        return storage.get(mapName);
-    }
-
-};
-
-const parsers: Dictionary<(record: MapRecord) => void> = {
-
-    geoJSON: function (record: GeoJSONMapRecord): void {
-        const source = record.source;
-        record.geoJSON = !isString(source)
-            ? source
-            : (typeof JSON !== 'undefined' && JSON.parse)
-            ? JSON.parse(source)
-            : (new Function('return (' + source + ');'))();
-    },
-
-    // Only perform parse to XML object here, which might be time
-    // consiming for large SVG.
-    // Although convert XML to zrender element is also time consiming,
-    // if we do it here, the clone of zrender elements has to be
-    // required. So we do it once for each geo instance, util real
-    // performance issues call for optimizing it.
-    svg: function (record: SVGMapRecord): void {
-        record.svgXML = parseXML(record.source as SVGMapSource);
-    }
-
-};
diff --git a/src/core/echarts.ts b/src/core/echarts.ts
index 20cf0f2..6bf60e8 100644
--- a/src/core/echarts.ts
+++ b/src/core/echarts.ts
@@ -68,7 +68,6 @@ import loadingDefault from '../loading/default';
 import Scheduler from './Scheduler';
 import lightTheme from '../theme/light';
 import darkTheme from '../theme/dark';
-import mapDataStorage from '../coord/geo/mapDataStorage';
 import {CoordinateSystemMaster, CoordinateSystemCreator, 
CoordinateSystemHostModel} from '../coord/CoordinateSystem';
 import { parseClassType } from '../util/clazz';
 import {ECEventProcessor} from '../util/ECEventProcessor';
@@ -105,6 +104,7 @@ import decal from '../visual/decal';
 import type {MorphDividingMethod} from 'zrender/src/tool/morphPath';
 import CanvasPainter from 'zrender/src/canvas/Painter';
 import SVGPainter from 'zrender/src/svg/Painter';
+import geoSourceManager from '../coord/geo/geoSourceManager';
 
 declare let global: any;
 
@@ -2831,26 +2831,19 @@ export function setCanvasCreator(creator: () => 
HTMLCanvasElement): void {
 }
 
 /**
- * The parameters and usage: see `mapDataStorage.registerMap`.
+ * The parameters and usage: see `geoSourceManager.registerMap`.
  * Compatible with previous `echarts.registerMap`.
  */
 export function registerMap(
-    mapName: Parameters<typeof mapDataStorage.registerMap>[0],
-    geoJson: Parameters<typeof mapDataStorage.registerMap>[1],
-    specialAreas?: Parameters<typeof mapDataStorage.registerMap>[2]
+    mapName: Parameters<typeof geoSourceManager.registerMap>[0],
+    geoJson: Parameters<typeof geoSourceManager.registerMap>[1],
+    specialAreas?: Parameters<typeof geoSourceManager.registerMap>[2]
 ): void {
-    mapDataStorage.registerMap(mapName, geoJson, specialAreas);
+    geoSourceManager.registerMap(mapName, geoJson, specialAreas);
 }
 
 export function getMap(mapName: string) {
-    // For backward compatibility, only return the first one.
-    const records = mapDataStorage.retrieveMap(mapName);
-    // FIXME support SVG, where return not only records[0].
-    return records && records[0] && {
-        // @ts-ignore
-        geoJson: records[0].geoJSON,
-        specialAreas: records[0].specialAreas
-    };
+    return geoSourceManager.getMapForUser(mapName);
 }
 
 export const registerTransform = registerExternalTransform;


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to