You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@echarts.apache.org by su...@apache.org on 2018/05/29 15:20:36 UTC

[incubator-echarts] 01/05: support svg geo (part I)

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

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

commit 68893b09910ed7b58d518e2b4513c2972aa869be
Author: sushuang <su...@gmail.com>
AuthorDate: Mon May 28 21:10:56 2018 +0800

    support svg geo (part I)
---
 src/component/helper/MapDraw.js   |  85 ++++++++++++++++------
 src/coord/geo/Geo.js              | 126 +++++++++++----------------------
 src/coord/geo/GeoModel.js         |   5 +-
 src/coord/geo/Region.js           |  10 ++-
 src/coord/geo/fix/diaoyuIsland.js |  16 ++---
 src/coord/geo/fix/geoCoord.js     |   8 +--
 src/coord/geo/fix/nanhai.js       |   6 +-
 src/coord/geo/fix/textCoord.js    |   8 +--
 src/coord/geo/geoCreator.js       |  80 +++++++--------------
 src/coord/geo/geoJSONLoader.js    |  94 +++++++++++++++++++++++++
 src/coord/geo/geoSVGLoader.js     | 143 ++++++++++++++++++++++++++++++++++++++
 src/coord/geo/geoSourceManager.js | 126 +++++++++++++++++++++++++++++++++
 src/coord/geo/mapDataStorage.js   | 104 +++++++++++++++++++++++++++
 src/echarts.js                    |  38 +++++-----
 14 files changed, 647 insertions(+), 202 deletions(-)

diff --git a/src/component/helper/MapDraw.js b/src/component/helper/MapDraw.js
index 0d00e2d..9ea90d9 100644
--- a/src/component/helper/MapDraw.js
+++ b/src/component/helper/MapDraw.js
@@ -22,6 +22,8 @@ import RoamController from './RoamController';
 import * as roamHelper from '../../component/helper/roamHelper';
 import {onIrrelevantElement} from '../../component/helper/cursorHelper';
 import * as graphic from '../../util/graphic';
+import geoSourceManager from '../../coord/geo/geoSourceManager';
+import {getUID} from '../../util/component';
 
 function getFixedItemStyle(model, scale) {
     var itemStyle = model.getItemStyle();
@@ -36,17 +38,17 @@ function getFixedItemStyle(model, scale) {
     return itemStyle;
 }
 
-function updateMapSelectHandler(mapDraw, mapOrGeoModel, group, api, fromView) {
-    group.off('click');
-    group.off('mousedown');
+function updateMapSelectHandler(mapDraw, mapOrGeoModel, regionsGroup, api, fromView) {
+    regionsGroup.off('click');
+    regionsGroup.off('mousedown');
 
     if (mapOrGeoModel.get('selectedMode')) {
 
-        group.on('mousedown', function () {
+        regionsGroup.on('mousedown', function () {
             mapDraw._mouseDownFlag = true;
         });
 
-        group.on('click', function (e) {
+        regionsGroup.on('click', function (e) {
             if (!mapDraw._mouseDownFlag) {
                 return;
             }
@@ -73,14 +75,14 @@ function updateMapSelectHandler(mapDraw, mapOrGeoModel, group, api, fromView) {
 
             api.dispatchAction(action);
 
-            updateMapSelected(mapOrGeoModel, group);
+            updateMapSelected(mapOrGeoModel, regionsGroup);
         });
     }
 }
 
-function updateMapSelected(mapOrGeoModel, group) {
+function updateMapSelected(mapOrGeoModel, regionsGroup) {
     // FIXME
-    group.eachChild(function (otherRegionEl) {
+    regionsGroup.eachChild(function (otherRegionEl) {
         zrUtil.each(otherRegionEl.__regions, function (region) {
             otherRegionEl.trigger(mapOrGeoModel.isSelected(region.name) ? 'emphasis' : 'normal');
         });
@@ -97,6 +99,12 @@ function MapDraw(api, updateGroup) {
     var group = new graphic.Group();
 
     /**
+     * @type {string}
+     * @private
+     */
+    this.uid = getUID('ec_map_draw');
+
+    /**
      * @type {module:echarts/component/helper/RoamController}
      * @private
      */
@@ -127,6 +135,26 @@ function MapDraw(api, updateGroup) {
      * @type {booelan}
      */
     this._mouseDownFlag;
+
+    /**
+     * @type {string}
+     */
+    this._mapName;
+
+    /**
+     * @type {boolean}
+     */
+    this._initialized;
+
+    /**
+     * @type {module:zrender/container/Group}
+     */
+    group.add(this._regionsGroup = new graphic.Group());
+
+    /**
+     * @type {module:zrender/container/Group}
+     */
+    group.add(this._backgroundGroup = new graphic.Group());
 }
 
 MapDraw.prototype = {
@@ -148,23 +176,26 @@ MapDraw.prototype = {
 
         var geo = mapOrGeoModel.coordinateSystem;
 
+        this._updateBackground(geo);
+
+        var regionsGroup = this._regionsGroup;
         var group = this.group;
 
         var scale = geo.scale;
-        var groupNewProp = {
+        var transform = {
             position: geo.position,
             scale: scale
         };
 
         // No animation when first draw or in action
-        if (!group.childAt(0) || payload) {
-            group.attr(groupNewProp);
+        if (!regionsGroup.childAt(0) || payload) {
+            group.attr(transform);
         }
         else {
-            graphic.updateProps(group, groupNewProp, mapOrGeoModel);
+            graphic.updateProps(group, transform, mapOrGeoModel);
         }
 
-        group.removeAll();
+        regionsGroup.removeAll();
 
         var itemStyleAccessPath = ['itemStyle'];
         var hoverItemStyleAccessPath = ['emphasis', 'itemStyle'];
@@ -306,22 +337,37 @@ MapDraw.prototype = {
                 {hoverSilentOnTouch: !!mapOrGeoModel.get('selectedMode')}
             );
 
-            group.add(regionGroup);
+            regionsGroup.add(regionGroup);
         });
 
         this._updateController(mapOrGeoModel, ecModel, api);
 
-        updateMapSelectHandler(this, mapOrGeoModel, group, api, fromView);
+        updateMapSelectHandler(this, mapOrGeoModel, regionsGroup, api, fromView);
 
-        updateMapSelected(mapOrGeoModel, group);
+        updateMapSelected(mapOrGeoModel, regionsGroup);
     },
 
     remove: function () {
-        this.group.removeAll();
+        this._regionsGroup.removeAll();
+        this._backgroundGroup.removeAll();
         this._controller.dispose();
+        this._mapName && geoSourceManager.removeGraphic(this._mapName, this.uid);
+        this._mapName = null;
         this._controllerHost = {};
     },
 
+    _updateBackground: function (geo) {
+        var mapName = geo.map;
+
+        if (this._mapName !== mapName) {
+            zrUtil.each(geoSourceManager.makeGraphic(mapName, this.uid), function (root) {
+                this._backgroundGroup.add(root);
+            }, this);
+        }
+
+        this._mapName = mapName;
+    },
+
     _updateController: function (mapOrGeoModel, ecModel, api) {
         var geo = mapOrGeoModel.coordinateSystem;
         var controller = this._controller;
@@ -366,9 +412,8 @@ MapDraw.prototype = {
             }));
 
             if (this._updateGroup) {
-                var group = this.group;
-                var scale = group.scale;
-                group.traverse(function (el) {
+                var scale = this.group.scale;
+                this._regionsGroup.traverse(function (el) {
                     if (el.type === 'text') {
                         el.attr('scale', [1 / scale[0], 1 / scale[1]]);
                     }
diff --git a/src/coord/geo/Geo.js b/src/coord/geo/Geo.js
index 83428af..8c6491c 100644
--- a/src/coord/geo/Geo.js
+++ b/src/coord/geo/Geo.js
@@ -19,33 +19,22 @@
 
 import * as zrUtil from 'zrender/src/core/util';
 import BoundingRect from 'zrender/src/core/BoundingRect';
-import parseGeoJson from './parseGeoJson';
 import View from '../View';
+import geoSourceManager from './geoSourceManager';
 
-import fixNanhai from './fix/nanhai';
-import fixTextCoord from './fix/textCoord';
-import fixGeoCoord from './fix/geoCoord';
-import fixDiaoyuIsland from './fix/diaoyuIsland';
-
-// Geo fix functions
-var geoFixFuncs = [
-    fixNanhai,
-    fixTextCoord,
-    fixGeoCoord,
-    fixDiaoyuIsland
-];
 
 /**
  * [Geo description]
- * @param {string} name Geo name
+ * For backward compatibility, the orginal interface:
+ * `name, map, geoJson, specialAreas, nameMap` is kept.
+ *
+ * @param {string|Object} name
  * @param {string} map Map type
- * @param {Object} geoJson
- * @param {Object} [specialAreas]
  *        Specify the positioned areas by left, top, width, height
  * @param {Object.<string, string>} [nameMap]
  *        Specify name alias
  */
-function Geo(name, map, geoJson, specialAreas, nameMap) {
+function Geo(name, map, nameMap) {
 
     View.call(this, name);
 
@@ -55,9 +44,20 @@ function Geo(name, map, geoJson, specialAreas, nameMap) {
      */
     this.map = map;
 
-    this._nameCoordMap = zrUtil.createHashMap();
+    var source = geoSourceManager.load(map, nameMap);
+
+    this._nameCoordMap = source.nameCoordMap;
+    this._regionsMap = source.nameCoordMap;
+
+    /**
+     * @readOnly
+     */
+    this.regions = source.regions;
 
-    this.loadGeoJson(geoJson, specialAreas, nameMap);
+    /**
+     * @type {module:zrender/src/core/BoundingRect}
+     */
+    this._rect = source.boundingRect;
 }
 
 Geo.prototype = {
@@ -86,61 +86,23 @@ Geo.prototype = {
         }
         return false;
     },
+
     /**
-     * @param {Object} geoJson
-     * @param {Object} [specialAreas]
-     *        Specify the positioned areas by left, top, width, height
-     * @param {Object.<string, string>} [nameMap]
-     *        Specify name alias
+     * @override
      */
-    loadGeoJson: function (geoJson, specialAreas, nameMap) {
-        // https://jsperf.com/try-catch-performance-overhead
-        try {
-            this.regions = geoJson ? parseGeoJson(geoJson) : [];
-        }
-        catch (e) {
-            throw 'Invalid geoJson format\n' + e.message;
-        }
-        specialAreas = specialAreas || {};
-        nameMap = nameMap || {};
-        var regions = this.regions;
-        var regionsMap = zrUtil.createHashMap();
-        for (var i = 0; i < regions.length; i++) {
-            var regionName = regions[i].name;
-            // Try use the alias in nameMap
-            regionName = nameMap.hasOwnProperty(regionName) ? nameMap[regionName] : regionName;
-            regions[i].name = regionName;
-
-            regionsMap.set(regionName, regions[i]);
-            // Add geoJson
-            this.addGeoCoord(regionName, regions[i].center);
-
-            // Some area like Alaska in USA map needs to be tansformed
-            // to look better
-            var specialArea = specialAreas[regionName];
-            if (specialArea) {
-                regions[i].transformTo(
-                    specialArea.left, specialArea.top, specialArea.width, specialArea.height
-                );
-            }
-        }
-
-        this._regionsMap = regionsMap;
-
-        this._rect = null;
-
-        zrUtil.each(geoFixFuncs, function (fixFunc) {
-            fixFunc(this);
-        }, this);
-    },
-
-    // Overwrite
     transformTo: function (x, y, width, height) {
         var rect = this.getBoundingRect();
 
+        // FIXME
+        // Should not name it as invertLng.
+        var invertLng = this.invertLng;
+
         rect = rect.clone();
-        // Longitute is inverted
-        rect.y = -rect.y - rect.height;
+
+        if (invertLng) {
+            // Longitute is inverted
+            rect.y = -rect.y - rect.height;
+        }
 
         var rawTransformable = this._rawTransformable;
 
@@ -150,8 +112,10 @@ Geo.prototype = {
 
         rawTransformable.decomposeTransform();
 
-        var scale = rawTransformable.scale;
-        scale[1] = -scale[1];
+        if (invertLng) {
+            var scale = rawTransformable.scale;
+            scale[1] = -scale[1];
+        }
 
         rawTransformable.updateTransform();
 
@@ -193,21 +157,11 @@ Geo.prototype = {
         return this._nameCoordMap.get(name);
     },
 
-    // Overwrite
+    /**
+     * @override
+     */
     getBoundingRect: function () {
-        if (this._rect) {
-            return this._rect;
-        }
-        var rect;
-
-        var regions = this.regions;
-        for (var i = 0; i < regions.length; i++) {
-            var regionRect = regions[i].getBoundingRect();
-            rect = rect || regionRect.clone();
-            rect.union(regionRect);
-        }
-        // FIXME Always return new ?
-        return (this._rect = rect || new BoundingRect(0, 0, 0, 0));
+        return this._rect;
     },
 
     /**
@@ -227,12 +181,12 @@ Geo.prototype = {
     },
 
     /**
-     * @inheritDoc
+     * @override
      */
     convertToPixel: zrUtil.curry(doConvert, 'dataToPoint'),
 
     /**
-     * @inheritDoc
+     * @override
      */
     convertFromPixel: zrUtil.curry(doConvert, 'pointToData')
 
diff --git a/src/coord/geo/GeoModel.js b/src/coord/geo/GeoModel.js
index 63f9257..fad9977 100644
--- a/src/coord/geo/GeoModel.js
+++ b/src/coord/geo/GeoModel.js
@@ -78,7 +78,9 @@ var GeoModel = ComponentModel.extend({
 
         // Aspect is width / height. Inited to be geoJson bbox aspect
         // This parameter is used for scale this aspect
-        aspectScale: 0.75,
+        // If svg used, aspectScale is 1 by default.
+        // aspectScale: 0.75,
+        aspectScale: null,
 
         ///// Layout with center and size
         // If you wan't to put map in a fixed size box with right aspect ratio
@@ -86,7 +88,6 @@ var GeoModel = ComponentModel.extend({
         // layoutCenter: [50%, 50%]
         // layoutSize: 100
 
-
         silent: false,
 
         // Map type
diff --git a/src/coord/geo/Region.js b/src/coord/geo/Region.js
index 43d9cd6..4de33b1 100644
--- a/src/coord/geo/Region.js
+++ b/src/coord/geo/Region.js
@@ -27,7 +27,7 @@ import * as vec2 from 'zrender/src/core/vector';
 import * as polygonContain from 'zrender/src/contain/polygon';
 
 /**
- * @param {string} name
+ * @param {string|Region} name
  * @param {Array} geometries
  * @param {Array.<number>} cp
  */
@@ -168,6 +168,14 @@ Region.prototype = {
             rect.x + rect.width / 2,
             rect.y + rect.height / 2
         ];
+    },
+
+    cloneShallow: function (name) {
+        name == null && (name = this.name);
+        var newRegion = new Region(name, this.geometries, this.center);
+        newRegion._rect = this._rect;
+        newRegion.transformTo = null; // Simply avoid to be called.
+        return newRegion;
     }
 };
 
diff --git a/src/coord/geo/fix/diaoyuIsland.js b/src/coord/geo/fix/diaoyuIsland.js
index 22fcd77..87b3f57 100644
--- a/src/coord/geo/fix/diaoyuIsland.js
+++ b/src/coord/geo/fix/diaoyuIsland.js
@@ -34,15 +34,11 @@ var points = [
     ]
 ];
 
-export default function (geo) {
-    if (geo.map === 'china') {
-        for (var i = 0, len = geo.regions.length; i < len; ++i) {
-            if (geo.regions[i].name === '台湾') {
-                geo.regions[i].geometries.push({
-                    type: 'polygon',
-                    exterior: points[0]
-                });
-            }
-        }
+export default function (mapType, region) {
+    if (mapType === 'china' && region.name === '台湾') {
+        region.geometries.push({
+            type: 'polygon',
+            exterior: points[0]
+        });
     }
 }
\ No newline at end of file
diff --git a/src/coord/geo/fix/geoCoord.js b/src/coord/geo/fix/geoCoord.js
index 69cc11a..b6e7080 100644
--- a/src/coord/geo/fix/geoCoord.js
+++ b/src/coord/geo/fix/geoCoord.js
@@ -17,21 +17,19 @@
 * under the License.
 */
 
-import * as zrUtil from 'zrender/src/core/util';
-
 var geoCoordMap = {
     'Russia': [100, 60],
     'United States': [-99, 38],
     'United States of America': [-99, 38]
 };
 
-export default function (geo) {
-    zrUtil.each(geo.regions, function (region) {
+export default function (mapType, region) {
+    if (mapType === 'world') {
         var geoCoord = geoCoordMap[region.name];
         if (geoCoord) {
             var cp = region.center;
             cp[0] = geoCoord[0];
             cp[1] = geoCoord[1];
         }
-    });
+    }
 }
\ No newline at end of file
diff --git a/src/coord/geo/fix/nanhai.js b/src/coord/geo/fix/nanhai.js
index f59c529..4eabeaf 100644
--- a/src/coord/geo/fix/nanhai.js
+++ b/src/coord/geo/fix/nanhai.js
@@ -51,9 +51,9 @@ for (var i = 0; i < points.length; i++) {
     }
 }
 
-export default function (geo) {
-    if (geo.map === 'china') {
-        geo.regions.push(new Region(
+export default function (mapType, regions) {
+    if (mapType === 'china') {
+        regions.push(new Region(
             '南海诸岛',
             zrUtil.map(points, function (exterior) {
                 return {
diff --git a/src/coord/geo/fix/textCoord.js b/src/coord/geo/fix/textCoord.js
index c2b858a..3ec837c 100644
--- a/src/coord/geo/fix/textCoord.js
+++ b/src/coord/geo/fix/textCoord.js
@@ -17,8 +17,6 @@
 * under the License.
 */
 
-import * as zrUtil from 'zrender/src/core/util';
-
 var coordsOffsetMap = {
     '南海诸岛' : [32, 80],
     // 全国
@@ -29,13 +27,13 @@ var coordsOffsetMap = {
     '天津': [5, 5]
 };
 
-export default function (geo) {
-    zrUtil.each(geo.regions, function (region) {
+export default function (mapType, region) {
+    if (mapType === 'china') {
         var coordFix = coordsOffsetMap[region.name];
         if (coordFix) {
             var cp = region.center;
             cp[0] += coordFix[0] / 10.5;
             cp[1] += -coordFix[1] / (10.5 / 0.75);
         }
-    });
+    }
 }
\ No newline at end of file
diff --git a/src/coord/geo/geoCreator.js b/src/coord/geo/geoCreator.js
index da94330..e740b65 100644
--- a/src/coord/geo/geoCreator.js
+++ b/src/coord/geo/geoCreator.js
@@ -23,6 +23,8 @@ import * as zrUtil from 'zrender/src/core/util';
 import Geo from './Geo';
 import * as layout from '../../util/layout';
 import * as numberUtil from '../../util/number';
+import geoSourceManager from './geoSourceManager';
+import mapDataStorage from './mapDataStorage';
 
 /**
  * Resize method bound to the geo
@@ -55,8 +57,7 @@ function resizeGeo(geoModel, api) {
     var viewWidth = api.getWidth();
     var viewHeight = api.getHeight();
 
-    var aspectScale = geoModel.get('aspectScale') || 0.75;
-    var aspect = rect.width / rect.height * aspectScale;
+    var aspect = rect.width / rect.height * this.aspectScale;
 
     var useCenterAndSize = false;
 
@@ -122,12 +123,6 @@ function setGeoCoords(geo, model) {
     });
 }
 
-if (__DEV__) {
-    var mapNotExistsError = function (name) {
-        console.error('Map ' + name + ' not exists. You can download map file on http://echarts.baidu.com/download-map.html');
-    };
-}
-
 var geoCreator = {
 
     // For deciding which dimensions to use when creating list data
@@ -139,17 +134,8 @@ var geoCreator = {
         // FIXME Create each time may be slow
         ecModel.eachComponent('geo', function (geoModel, idx) {
             var name = geoModel.get('map');
-            var mapData = echarts.getMap(name);
-            if (__DEV__) {
-                if (!mapData) {
-                    mapNotExistsError(name);
-                }
-            }
-            var geo = new Geo(
-                name + idx, name,
-                mapData && mapData.geoJson, mapData && mapData.specialAreas,
-                geoModel.get('nameMap')
-            );
+            var geo = new Geo(name + idx, name, geoModel.get('nameMap'));
+
             geo.zoomLimit = geoModel.get('scaleLimit');
             geoList.push(geo);
 
@@ -158,6 +144,20 @@ var geoCreator = {
             geoModel.coordinateSystem = geo;
             geo.model = geoModel;
 
+            // FIXME ???
+            var aspectScale = geoModel.get('aspectScale');
+            var invertLng = true;
+            var mapRecords = mapDataStorage.retrieveMap(name);
+            if (mapRecords && mapRecords[0] && mapRecords[0].type === 'svg') {
+                aspectScale == null && (aspectScale = 1);
+                invertLng = false;
+            }
+            else {
+                aspectScale == null && (aspectScale = 0.75);
+            }
+            geo.aspectScale = aspectScale;
+            geo.invertLng = invertLng;
+
             // Inject resize method
             geo.resize = resizeGeo;
 
@@ -184,21 +184,11 @@ var geoCreator = {
         });
 
         zrUtil.each(mapModelGroupBySeries, function (mapSeries, mapType) {
-            var mapData = echarts.getMap(mapType);
-            if (__DEV__) {
-                if (!mapData) {
-                    mapNotExistsError(mapSeries[0].get('map'));
-                }
-            }
-
             var nameMapList = zrUtil.map(mapSeries, function (singleMapSeries) {
                 return singleMapSeries.get('nameMap');
             });
-            var geo = new Geo(
-                mapType, mapType,
-                mapData && mapData.geoJson, mapData && mapData.specialAreas,
-                zrUtil.mergeAll(nameMapList)
-            );
+            var geo = new Geo(mapType, mapType, zrUtil.mergeAll(nameMapList));
+
             geo.zoomLimit = zrUtil.retrieve.apply(null, zrUtil.map(mapSeries, function (singleMapSeries) {
                 return singleMapSeries.get('scaleLimit');
             }));
@@ -229,34 +219,18 @@ var geoCreator = {
     getFilledRegions: function (originRegionArr, mapName, nameMap) {
         // Not use the original
         var regionsArr = (originRegionArr || []).slice();
-        nameMap = nameMap || {};
-
-        var map = echarts.getMap(mapName);
-        var geoJson = map && map.geoJson;
-        if (!geoJson) {
-            if (__DEV__) {
-                mapNotExistsError(mapName);
-            }
-            return originRegionArr;
-        }
 
         var dataNameMap = zrUtil.createHashMap();
-        var features = geoJson.features;
         for (var i = 0; i < regionsArr.length; i++) {
             dataNameMap.set(regionsArr[i].name, regionsArr[i]);
         }
 
-        for (var i = 0; i < features.length; i++) {
-            var name = features[i].properties.name;
-            if (!dataNameMap.get(name)) {
-                if (nameMap.hasOwnProperty(name)) {
-                    name = nameMap[name];
-                }
-                regionsArr.push({
-                    name: name
-                });
-            }
-        }
+        var source = geoSourceManager.load(mapName, nameMap);
+        zrUtil.each(source.regions, function (region) {
+            var name = region.name;
+            !dataNameMap.get(name) && regionsArr.push({name: name});
+        });
+
         return regionsArr;
     }
 };
diff --git a/src/coord/geo/geoJSONLoader.js b/src/coord/geo/geoJSONLoader.js
new file mode 100644
index 0000000..5f0c05a
--- /dev/null
+++ b/src/coord/geo/geoJSONLoader.js
@@ -0,0 +1,94 @@
+/*
+* 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';
+
+var inner = makeInner();
+
+export default {
+
+    /**
+     * @param {string} mapName
+     * @param {Object} mapRecord {specialAreas, geoJSON}
+     * @return {Object} {regions, boundingRect}
+     */
+    load: function (mapName, mapRecord) {
+
+        var parsed = inner(mapRecord).parsed;
+
+        if (parsed) {
+            return parsed;
+        }
+
+        var specialAreas = mapRecord.specialAreas || {};
+        var geoJSON = mapRecord.geoJSON;
+        var regions;
+
+        // https://jsperf.com/try-catch-performance-overhead
+        try {
+            regions = geoJSON ? parseGeoJson(geoJSON) : [];
+        }
+        catch (e) {
+            throw new Error('Invalid geoJson format\n' + e.message);
+        }
+
+        each(regions, function (region) {
+            var 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
+            var specialArea = specialAreas[regionName];
+            if (specialArea) {
+                region.transformTo(
+                    specialArea.left, specialArea.top, specialArea.width, specialArea.height
+                );
+            }
+        });
+
+        fixNanhai(mapName, regions);
+
+        return (inner(mapRecord).parsed = {
+            regions: regions,
+            boundingRect: getBoundingRect(regions)
+        });
+    }
+};
+
+function getBoundingRect(regions) {
+    var rect;
+    for (var i = 0; i < regions.length; i++) {
+        var regionRect = regions[i].getBoundingRect();
+        rect = rect || regionRect.clone();
+        rect.union(regionRect);
+    }
+    return rect;
+}
+
diff --git a/src/coord/geo/geoSVGLoader.js b/src/coord/geo/geoSVGLoader.js
new file mode 100644
index 0000000..98eab03
--- /dev/null
+++ b/src/coord/geo/geoSVGLoader.js
@@ -0,0 +1,143 @@
+/*
+* 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 {parseSVG, makeViewBoxTransform} from 'zrender/src/tool/parseSVG';
+import Group from 'zrender/src/container/Group';
+import Rect from 'zrender/src/graphic/shape/Rect';
+import {assert, createHashMap} from 'zrender/src/core/util';
+import BoundingRect from 'zrender/src/core/BoundingRect';
+import {makeInner} from '../../util/model';
+
+var inner = makeInner();
+
+export default {
+
+    /**
+     * @param {string} mapName
+     * @param {Object} mapRecord {specialAreas, geoJSON}
+     * @return {Object} {root, boundingRect}
+     */
+    load: function (mapName, mapRecord) {
+        var originRoot = inner(mapRecord).originRoot;
+        if (originRoot) {
+            return {
+                root: originRoot,
+                boundingRect: inner(mapRecord).boundingRect
+            };
+        }
+
+        var graphic = buildGraphic(mapRecord);
+
+        inner(mapRecord).originRoot = graphic.root;
+        inner(mapRecord).boundingRect = graphic.boundingRect;
+
+        return graphic;
+    },
+
+    makeGraphic: function (mapName, mapRecord, hostKey) {
+        // For performance consideration (in large SVG), graphic only maked
+        // when necessary and reuse them according to hostKey.
+        var field = inner(mapRecord);
+        var rootMap = field.rootMap || (field.rootMap = createHashMap());
+
+        var root = rootMap.get(hostKey);
+        if (root) {
+            return root;
+        }
+
+        var originRoot = field.originRoot;
+        var boundingRect = field.boundingRect;
+
+        // 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 rootMap.set(hostKey, root);
+    },
+
+    removeGraphic: function (mapName, mapRecord, hostKey) {
+        var field = inner(mapRecord);
+        var rootMap = field.rootMap;
+        rootMap && rootMap.removeKey(hostKey);
+        if (hostKey === field.originRootHostKey) {
+            field.originRootHostKey = null;
+        }
+    }
+};
+
+function buildGraphic(mapRecord, boundingRect) {
+    var svgXML = mapRecord.svgXML;
+    var result;
+    var root;
+
+    try {
+        result = svgXML && parseSVG(svgXML, {
+            ignoreViewBox: true,
+            ignoreRootClip: true
+        }) || {};
+        root = result.root;
+        assert(root != null);
+    }
+    catch (e) {
+        throw new Error('Invalid svg format\n' + e.message);
+    }
+
+    var svgWidth = result.width;
+    var svgHeight = result.height;
+    var viewBoxRect = result.viewBoxRect;
+
+    if (!boundingRect) {
+        boundingRect = (svgWidth == null || svgHeight == null)
+            // If svg width / height not specified, calculate
+            // bounding rect as the width / height
+            ? root.getBoundingRect()
+            : new BoundingRect(0, 0, 0, 0);
+
+        if (svgWidth != null) {
+            boundingRect.width = svgWidth;
+        }
+        if (svgHeight != null) {
+            boundingRect.height = svgHeight;
+        }
+    }
+
+    if (viewBoxRect) {
+        var viewBoxTransform = makeViewBoxTransform(viewBoxRect, boundingRect.width, boundingRect.height);
+        var elRoot = root;
+        root = new Group();
+        root.add(elRoot);
+        elRoot.scale = viewBoxTransform.scale;
+        elRoot.position = viewBoxTransform.position;
+    }
+
+    root.setClipPath(new Rect({
+        shape: boundingRect.plain()
+    }));
+
+    return {
+        root: root,
+        boundingRect: boundingRect
+    };
+}
diff --git a/src/coord/geo/geoSourceManager.js b/src/coord/geo/geoSourceManager.js
new file mode 100644
index 0000000..53ce1f8
--- /dev/null
+++ b/src/coord/geo/geoSourceManager.js
@@ -0,0 +1,126 @@
+/*
+* 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 {__DEV__} from '../../config';
+import {each, createHashMap} from 'zrender/src/core/util';
+import mapDataStorage from './mapDataStorage';
+import geoJSONLoader from './geoJSONLoader';
+import geoSVGLoader from './geoSVGLoader';
+import BoundingRect from 'zrender/src/core/BoundingRect';
+
+var loaders = {
+    geoJSON: geoJSONLoader,
+    svg: geoSVGLoader
+};
+
+export default {
+
+    /**
+     * @param {string} mapName
+     * @param {Object} nameMap
+     * @return {Object} source {regions, regionsMap, nameCoordMap, boundingRect}
+     */
+    load: function (mapName, nameMap) {
+        var regions = [];
+        var regionsMap = createHashMap();
+        var nameCoordMap = createHashMap();
+        var boundingRect;
+        var mapRecords = retrieveMap(mapName);
+
+        each(mapRecords, function (record) {
+            var singleSource = loaders[record.type].load(mapName, record);
+
+            each(singleSource.regions, function (region) {
+                var 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);
+            });
+
+            var rect = singleSource.boundingRect;
+            if (rect) {
+                boundingRect
+                    ? boundingRect.union(rect)
+                    : (boundingRect = rect.clone());
+            }
+        });
+
+        return {
+            regions: regions,
+            regionsMap: regionsMap,
+            nameCoordMap: nameCoordMap,
+            // FIXME Always return new ?
+            boundingRect: boundingRect || new BoundingRect(0, 0, 0, 0)
+        };
+    },
+
+    /**
+     * @param {string} mapName
+     * @param {string} hostKey For cache.
+     * @return {Array.<module:zrender/Element>} Roots.
+     */
+    makeGraphic: makeInvoker('makeGraphic'),
+
+    /**
+     * @param {string} mapName
+     * @param {string} hostKey For cache.
+     */
+    removeGraphic: makeInvoker('removeGraphic')
+};
+
+function makeInvoker(methodName) {
+    return function (mapName, hostKey) {
+        var mapRecords = retrieveMap(mapName);
+        var results = [];
+
+        each(mapRecords, function (record) {
+            var method = loaders[record.type][methodName];
+            method && results.push(method(mapName, record, hostKey));
+        });
+
+        return results;
+    };
+}
+
+function mapNotExistsError(mapName) {
+    if (__DEV__) {
+        console.error(
+            'Map ' + mapName + ' not exists. You can download map file on http://echarts.baidu.com/download-map.html'
+        );
+    }
+}
+
+function retrieveMap(mapName) {
+    var mapRecords = mapDataStorage.retrieveMap(mapName) || [];
+
+    if (__DEV__) {
+        if (!mapRecords.length) {
+            mapNotExistsError(mapName);
+        }
+    }
+
+    return mapRecords;
+}
+
diff --git a/src/coord/geo/mapDataStorage.js b/src/coord/geo/mapDataStorage.js
new file mode 100644
index 0000000..8f6de57
--- /dev/null
+++ b/src/coord/geo/mapDataStorage.js
@@ -0,0 +1,104 @@
+/*
+* 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 {__DEV__} from '../../config';
+import {createHashMap, isString, isArray, each, assert} from 'zrender/src/core/util';
+import {parseXML} from 'zrender/src/tool/parseSVG';
+
+
+var storage = createHashMap();
+
+// For minimize the code size of common echarts package,
+// do not put too much logic in this module.
+
+export default {
+
+    // The format of record: see `echarts.registerMap`.
+    // Compatible with previous `echarts.registerMap`.
+    registerMap: function (mapName, rawGeoJson, rawSpecialAreas) {
+
+        var records;
+
+        if (isArray(rawGeoJson)) {
+            records = rawGeoJson;
+        }
+        else if (rawGeoJson.svg) {
+            records = [{
+                type: 'svg',
+                source: rawGeoJson.svg,
+                specialAreas: rawGeoJson.specialAreas
+            }];
+        }
+        else {
+            // Backward compatibility.
+            if (rawGeoJson.geoJson && !rawGeoJson.features) {
+                rawSpecialAreas = rawGeoJson.specialAreas;
+                rawGeoJson = rawGeoJson.geoJson;
+            }
+            records = [{
+                type: 'geoJSON',
+                source: rawGeoJson,
+                specialAreas: rawSpecialAreas
+            }];
+        }
+
+        each(records, function (record) {
+            var type = record.type;
+            type === 'geoJson' && (type = record.type = 'geoJSON');
+
+            var parse = parsers[type];
+
+            if (__DEV__) {
+                assert(parse, 'Illegal map type: ' + type);
+            }
+
+            parse(record);
+        });
+
+        return storage.set(mapName, records);
+    },
+
+    retrieveMap: function (mapName) {
+        return storage.get(mapName);
+    }
+
+};
+
+var parsers = {
+
+    geoJSON: function (record) {
+        var 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) {
+        record.svgXML = parseXML(record.source);
+    }
+
+};
diff --git a/src/echarts.js b/src/echarts.js
index 019ef6b..dec7148 100644
--- a/src/echarts.js
+++ b/src/echarts.js
@@ -43,6 +43,7 @@ import Scheduler from './stream/Scheduler';
 import lightTheme from './theme/light';
 import darkTheme from './theme/dark';
 import './component/dataset';
+import mapDataStorage from './coord/geo/mapDataStorage';
 
 var assert = zrUtil.assert;
 var each = zrUtil.each;
@@ -1720,8 +1721,6 @@ var idBase = new Date() - 0;
 var groupIdBase = new Date() - 0;
 var DOM_ATTRIBUTE_KEY = '_echarts_instance_';
 
-var mapDataStores = {};
-
 function enableConnect(chart) {
     var STATUS_PENDING = 0;
     var STATUS_UPDATING = 1;
@@ -2115,10 +2114,10 @@ export function setCanvasCreator(creator) {
 
 /**
  * @param {string} mapName
- * @param {Object|string} geoJson
+ * @param {Array.<Object>|Object|string} geoJson
  * @param {Object} [specialAreas]
  *
- * @example
+ * @example GeoJSON
  *     $.get('USA.json', function (geoJson) {
  *         echarts.registerMap('USA', geoJson);
  *         // Or
@@ -2127,20 +2126,20 @@ export function setCanvasCreator(creator) {
  *             specialAreas: {}
  *         })
  *     });
+ *
+ *     $.get('airport.svg', function (svg) {
+ *         echarts.registerMap('airport', {
+ *             svg: svg
+ *         }
+ *     });
+ *
+ *     echarts.registerMap('eu', [
+ *         {svg: eu-topographic.svg},
+ *         {geoJSON: eu.json}
+ *     ])
  */
 export function registerMap(mapName, geoJson, specialAreas) {
-    if (geoJson.geoJson && !geoJson.features) {
-        specialAreas = geoJson.specialAreas;
-        geoJson = geoJson.geoJson;
-    }
-    if (typeof geoJson === 'string') {
-        geoJson = (typeof JSON !== 'undefined' && JSON.parse)
-            ? JSON.parse(geoJson) : (new Function('return (' + geoJson + ');'))();
-    }
-    mapDataStores[mapName] = {
-        geoJson: geoJson,
-        specialAreas: specialAreas
-    };
+    mapDataStorage.registerMap(mapName, geoJson, specialAreas);
 }
 
 /**
@@ -2148,7 +2147,12 @@ export function registerMap(mapName, geoJson, specialAreas) {
  * @return {Object}
  */
 export function getMap(mapName) {
-    return mapDataStores[mapName];
+    // For backward compatibility, only return the first one.
+    var records = mapDataStorage.retrieveMap(mapName);
+    return records && records[0] && {
+        geoJson: records[0].geoJSON,
+        specialAreas: records[0].specialAreas
+    };
 }
 
 registerVisual(PRIORITY_VISUAL_GLOBAL, seriesColor);

-- 
To stop receiving notification emails like this one, please contact
sushuang@apache.org.

---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@echarts.apache.org
For additional commands, e-mail: commits-help@echarts.apache.org