/* * 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 * as zrUtil from 'zrender/src/core/util'; import BoundingRect from 'zrender/src/core/BoundingRect'; import View from '../View'; import geoSourceManager from './geoSourceManager'; import { GeoJSONRegion, Region } from './Region'; import { GeoProjection, GeoResource, NameMap } from './geoTypes'; import GlobalModel from '../../model/Global'; import { ParsedModelFinder, ParsedModelFinderKnown, SINGLE_REFERRING } from '../../util/model'; import GeoModel from './GeoModel'; import { resizeGeoType } from './geoCreator'; import { warn } from '../../util/log'; const GEO_DEFAULT_PARAMS: { [type in GeoResource['type']]: { aspectScale: number; invertLongitute: boolean; } } = { 'geoJSON': { aspectScale: 0.75, invertLongitute: true }, 'geoSVG': { aspectScale: 1, invertLongitute: false } } as const; export const geo2DDimensions = ['lng', 'lat']; class Geo extends View { dimensions = geo2DDimensions; type = 'geo'; // map type readonly map: string; readonly resourceType: GeoResource['type']; // Only store specified name coord via `addGeoCoord`. private _nameCoordMap: zrUtil.HashMap = zrUtil.createHashMap(); private _regionsMap: zrUtil.HashMap; private _invertLongitute: boolean; readonly regions: Region[]; readonly aspectScale: number; projection: GeoProjection; // Injected outside model: GeoModel; resize: resizeGeoType; constructor( name: string, map: string, opt: { projection?: GeoProjection; // Specify name alias nameMap?: NameMap; nameProperty?: string; aspectScale?: number; } ) { super(name); this.map = map; let projection = opt.projection; const source = geoSourceManager.load( map, opt.nameMap, opt.nameProperty ); const resource = geoSourceManager.getGeoResource(map); const resourceType = this.resourceType = resource ? resource.type : null; const regions = this.regions = source.regions; const defaultParams = GEO_DEFAULT_PARAMS[resource.type]; this._regionsMap = source.regionsMap; this.regions = source.regions; if (__DEV__ && projection) { // Do some check if (resourceType === 'geoSVG') { if (__DEV__) { warn(`Map ${map} with SVG source can't use projection. Only GeoJSON source supports projection.`); } projection = null; } if (!(projection.project && projection.unproject)) { if (__DEV__) { warn('project and unproject must be both provided in the projeciton.'); } projection = null; } } this.projection = projection; let boundingRect; if (projection) { // Can't reuse the raw bounding rect for (let i = 0; i < regions.length; i++) { const regionRect = (regions[i] as GeoJSONRegion).getBoundingRect(projection); boundingRect = boundingRect || regionRect.clone(); boundingRect.union(regionRect); } } else { boundingRect = source.boundingRect; } this.setBoundingRect(boundingRect.x, boundingRect.y, boundingRect.width, boundingRect.height); // aspectScale and invertLongitute actually is the parameters default raw projection. // So we ignore them if projection is given. // Ignore default aspect scale if projection exits. this.aspectScale = projection ? 1 : zrUtil.retrieve2(opt.aspectScale, defaultParams.aspectScale); // Not invert longitude if projection exits. this._invertLongitute = projection ? false : defaultParams.invertLongitute; } protected _transformTo(x: number, y: number, width: number, height: number): void { let rect = this.getBoundingRect(); const invertLongitute = this._invertLongitute; rect = rect.clone(); if (invertLongitute) { // Longitude is inverted. rect.y = -rect.y - rect.height; } const rawTransformable = this._rawTransformable; rawTransformable.transform = rect.calculateTransform( new BoundingRect(x, y, width, height) ); const rawParent = rawTransformable.parent; rawTransformable.parent = null; rawTransformable.decomposeTransform(); rawTransformable.parent = rawParent; if (invertLongitute) { rawTransformable.scaleY = -rawTransformable.scaleY; } this._updateTransform(); } getRegion(name: string): Region { return this._regionsMap.get(name); } getRegionByCoord(coord: number[]): Region { const regions = this.regions; for (let i = 0; i < regions.length; i++) { const region = regions[i]; if (region.type === 'geoJSON' && (region as GeoJSONRegion).contain(coord)) { return regions[i]; } } } /** * Add geoCoord for indexing by name */ addGeoCoord(name: string, geoCoord: number[]): void { this._nameCoordMap.set(name, geoCoord); } /** * Get geoCoord by name */ getGeoCoord(name: string): number[] { const region = this._regionsMap.get(name); // Calculate center only on demand. return this._nameCoordMap.get(name) || (region && region.getCenter()); } dataToPoint(data: number[] | string, noRoam?: boolean, out?: number[]): number[] { if (zrUtil.isString(data)) { // Map area name to geoCoord data = this.getGeoCoord(data); } if (data) { const projection = this.projection; if (projection) { // projection may return null point. data = projection.project(data); } return data && this.projectedToPoint(data, noRoam, out); } } pointToData(point: number[]) { const projection = this.projection; if (projection) { // projection may return null point. point = projection.unproject(point); } return point && this.pointToProjected(point); } /** * Point to projected data. Same with pointToData when projection is used. */ pointToProjected(point: number[]) { return super.pointToData(point); } projectedToPoint(projected: number[], noRoam?: boolean, out?: number[]) { return super.dataToPoint(projected, noRoam, out); } convertToPixel(ecModel: GlobalModel, finder: ParsedModelFinder, value: number[]): number[] { const coordSys = getCoordSys(finder); return coordSys === this ? coordSys.dataToPoint(value) : null; } convertFromPixel(ecModel: GlobalModel, finder: ParsedModelFinder, pixel: number[]): number[] { const coordSys = getCoordSys(finder); return coordSys === this ? coordSys.pointToData(pixel) : null; } }; zrUtil.mixin(Geo, View); function getCoordSys(finder: ParsedModelFinderKnown): Geo { const geoModel = finder.geoModel as GeoModel; const seriesModel = finder.seriesModel; return geoModel ? geoModel.coordinateSystem : seriesModel ? ( seriesModel.coordinateSystem as Geo // For map series. || ( (seriesModel.getReferringComponents('geo', SINGLE_REFERRING).models[0] || {} ) as GeoModel).coordinateSystem ) : null; } export default Geo;