/* * 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, hasOwn } from 'zrender/src/core/util'; import parseGeoJson from './parseGeoJson'; // Built-in GEO fixer. import fixNanhai from './fix/nanhai'; import fixTextCoord from './fix/textCoord'; import fixDiaoyuIsland from './fix/diaoyuIsland'; import BoundingRect from 'zrender/src/core/BoundingRect'; import { GeoJSONRegion } from './Region'; import { GeoJSON, GeoJSONCompressed, GeoJSONSourceInput, GeoResource, GeoSpecialAreas, NameMap } from './geoTypes'; const DEFAULT_NAME_PROPERTY = 'name' as const; export class GeoJSONResource implements GeoResource { readonly type = 'geoJSON'; private _geoJSON: GeoJSON | GeoJSONCompressed; private _specialAreas: GeoSpecialAreas; private _mapName: string; private _parsedMap = createHashMap<{ regions: GeoJSONRegion[]; boundingRect: BoundingRect; }, string>(); 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); } /** * @param nameMap can be null/undefined * @param nameProperty can be null/undefined */ load(nameMap: NameMap, nameProperty: string) { nameProperty = nameProperty || DEFAULT_NAME_PROPERTY; let parsed = this._parsedMap.get(nameProperty); if (!parsed) { const rawRegions = this._parseToRegions(nameProperty); parsed = this._parsedMap.set(nameProperty, { regions: rawRegions, boundingRect: calculateBoundingRect(rawRegions) }); } const regionsMap = createHashMap(); const finalRegions: GeoJSONRegion[] = []; each(parsed.regions, function (region) { let regionName = region.name; // Try use the alias in geoNameMap if (nameMap && hasOwn(nameMap, regionName)) { region = region.cloneShallow(regionName = nameMap[regionName]); } finalRegions.push(region); regionsMap.set(regionName, region); }); return { regions: finalRegions, boundingRect: parsed.boundingRect || new BoundingRect(0, 0, 0, 0), regionsMap: regionsMap }; } private _parseToRegions(nameProperty: string): GeoJSONRegion[] { 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); 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: GeoJSONRegion[]): 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 + ');'))(); }