123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724 |
- /*
- * This file is part of OsmInEdit, released under ISC license (see LICENSE.md)
- *
- * Copyright (c) Adrien Pavie 2019
- * Copyright (c) Daimler AG 2019
- *
- * Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies.
- *
- * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
- *
- */
- import React, { Component } from 'react';
- import 'leaflet/dist/leaflet.css';
- import L from 'leaflet';
- import 'leaflet-hash';
- import { Map, TileLayer, AttributionControl, ScaleControl } from 'react-leaflet';
- import Spinner from 'react-bootstrap/Spinner';
- import PubSub from 'pubsub-js';
- import Body from './Body';
- import PACKAGE from '../../package.json';
- import Features from './layers/Features';
- import FloorImagery from './layers/FloorImagery';
- import MoveableObject from './layers/MoveableObject';
- import MapStyler from '../model/mapcss/MapStyler';
- import LevelSelector from './common/LevelSelector';
- import NorthPointer from './common/NorthPointer';
- import HistoryTrack from './layers/HistoryTrack';
- import POIObject from './layers/POIObject';
- import MapUtil from './utils/MapUtil';
- /*
- * Extend leaflet hash for handling level value
- */
- L.Hash.parseHash = function (hash) {
- if (hash.indexOf('#') === 0) {
- hash = hash.substr(1);
- }
- var args = hash.split("/");
- if (args.length >= 3 && args.length <= 4) {
- var zoom = parseInt(args[0], 10),
- lat = parseFloat(args[1]),
- lon = parseFloat(args[2]),
- level = args.length === 4 ? parseInt(args[3], 10) : 0;
- if (isNaN(zoom) || isNaN(lat) || isNaN(lon)) {
- return false;
- } else {
- return {
- center: new L.LatLng(lat, lon),
- zoom: zoom,
- level: isNaN(level) ? 0 : level
- };
- }
- } else {
- return false;
- }
- };
- L.Hash.prototype.parseHash = L.Hash.parseHash;
- L.Hash.formatHash = function (map) {
- var center = map.getCenter(),
- zoom = map.getZoom(),
- precision = Math.max(0, Math.ceil(Math.log(zoom) / Math.LN2));
- return "#" + [zoom,
- center.lat.toFixed(precision),
- center.lng.toFixed(precision),
- this._level || "0"
- ].join("/");
- };
- L.Hash.prototype.formatHash = L.Hash.formatHash;
- L.Hash.prototype.setLevel = function (lvl) {
- if (this._level !== lvl) {
- this._level = lvl;
- var hash = this.formatHash(this.map);
- window.location.replace(hash);
- this.lastHash = hash;
- }
- };
- L.Hash.prototype.update = function () {
- var hash = window.location.hash;
- if (hash === this.lastHash) {
- return;
- }
- var parsed = this.parseHash(hash);
- if (parsed) {
- this.movingMap = true;
- this.map.setView(parsed.center, parsed.zoom);
- this.movingMap = false;
- PubSub.publish("body.level.set", { level: parsed.level });
- } else {
- this.onMapMove(this.map);
- }
- };
- /**
- * Map component handles the whole map and associated widgets.
- */
- class MyMap extends Component {
- constructor() {
- super();
- this.state = {
- loading: false,
- dataready: false,
- initHisTrack: false,
- center: null,
- zoom: null,
- minZoom: null,
- maxZoom: null,
- maxBounds: null,
- };
- this.mapStyler = new MapStyler();
- this.loadedArea = null;
- }
- /**
- * Alert this component that its size has changed
- */
- invalidateSize() {
- if (this.elem && this.elem.leafletElement) {
- this.elem.leafletElement.invalidateSize();
- this.map = this.elem.leafletElement;
- // 设定初始化的值
- this.setState({
- center: this.props.center,
- zoom: this.props.zoom,
- minZoom: this.props.minZoom,
- maxZoom: this.props.maxZoom,
- maxBounds: this.props.maxBounds
- })
- // // 设置地图中心、缩放
- // if (this.props.center) {
- // this.setView(this.props.center, this.props.zoom);
- // } else {
- // this.setZoom(this.props.zoom);
- // }
- // // 设置缩放范围
- // this.setZoomRange(this.props.minZoom, this.props.maxZoom);
- // // 设置视图范围
- // if (this.props.maxBounds.length === 2) {
- // this.setMaxBounds(this.props.maxBounds)
- // }
- }
- }
- /**
- * Clean up map after changeset upload
- */
- cleanUp() {
- this.loadedArea = null;
- this.setState({ loading: false, dataready: false });
- }
- /**
- * 修改地图状态
- */
- setView(center, zoom) {
- if (this.elem && this.elem.leafletElement) {
- this.elem.leafletElement.setView(center, zoom);
- // this.setState({
- // center: latlng,
- // zoom: zoom
- // })
- } else {
- console.error('setView')
- }
- }
- setCenter(center) {
- if (this.elem && this.elem.leafletElement) {
- this.elem.leafletElement.pathTo(center);
- // this.setState({
- // center: latlng
- // })
- } else {
- console.error('setCenter')
- }
- }
- setZoom(zoom) {
- if (this.elem && this.elem.leafletElement) {
- this.elem.leafletElement.setZoom(zoom);
- // this.setState({
- // zoom: zoom
- // })
- } else {
- console.error('setZoom')
- }
- }
- setMinZoom(zoom) {
- if (this.elem && this.elem.leafletElement) {
- this.elem.leafletElement.setMinZoom(zoom);
- // this.setState({
- // minZoom: zoom
- // })
- } else {
- console.error('setMinZoom')
- }
- }
- setMaxZoom(zoom) {
- if (this.elem && this.elem.leafletElement) {
- this.elem.leafletElement.setMaxZoom(zoom);
- // this.setState({
- // maxZoom: zoom
- // })
- } else {
- console.error('setMaxZoom')
- }
- }
- setZoomRange(min, max) {
- if (this.elem && this.elem.leafletElement) {
- this.elem.leafletElement.setMinZoom(min);
- this.elem.leafletElement.setMaxZoom(max);
- // this.setState({
- // minZoom: min,
- // maxZoom: max
- // })
- } else {
- console.error('setZoomRange')
- }
- }
- setMaxBounds(corner) {
- let corner1 = L.latLng(corner[0]), corner2 = L.latLng(corner[1]);
- let bounds = L.latLngBounds(corner1, corner2)
- if (this.elem && this.elem.leafletElement) {
- this.elem.leafletElement.setMaxBounds(bounds)
- // this.setState({
- // maxBounds: bounds
- // })
- } else {
- console.error('setMaxBounds')
- }
- }
- /**
- * @param {Array} [10, 20] [-20, 10]
- */
- pathTo(center) {
- if (this.elem && this.elem.leafletElement) {
- this.elem.leafletElement.pathTo(center)
- } else {
- console.error('pathTo')
- }
- }
- /**
- * @param {Array/Object} center [10, 20] {lat: 10, lng: 10}
- * @param {Number} zoom 18
- */
- flyTo(center, zoom) {
- if (this.elem && this.elem.leafletElement) {
- this.elem.leafletElement.flyTo(center, zoom)
- } else {
- console.error('flyTo')
- }
- }
- /**
- * 获取地图状态
- */
- /**
- * Get the coordinates of map center
- * @return {LatLng} Coordinates of map center (or null if not ready)
- */
- getCenter() {
- return (this.elem && this.elem.leafletElement) ? this.elem.leafletElement.getCenter() : null;
- }
- getZoom() {
- return (this.elem && this.elem.leafletElement) ? this.elem.leafletElement.getZoom() : null;
- }
- getMinZoom() {
- return (this.elem && this.elem.leafletElement) ? this.elem.leafletElement.getMinZoom() : null;
- }
- getMaxZoom() {
- return (this.elem && this.elem.leafletElement) ? this.elem.leafletElement.getMaxZoom() : null;
- }
- getZoomRange() {
- return (this.elem && this.elem.leafletElement) ? [this.elem.leafletElement.getMinZoom(), this.elem.leafletElement.getMaxZoom()] : [];
- }
- getContainerSize() {
- return (this.elem && this.elem.leafletElement) ? this.elem.leafletElement.getSize() : null;
- }
- /**
- * Get the bounding box of currently shown area on map
- * @return {LatLngBounds} Bounding box of the map
- */
- getBounds() {
- return (this.elem && this.elem.leafletElement) ? this.elem.leafletElement.getBounds() : null;
- }
- /**
- * Is the map currently loading data ?
- * @return {boolean} True if loading
- */
- isLoading() {
- return this.state.loading;
- }
- /**
- * Event handler when map moves
- * @private
- */
- async _loadData(bounds) {
- // (this.props.datalocked || (window.CONFIG.always_authenticated && !window.editor_user))
- if (this.props.datalocked) {
- return new Promise(resolve => {
- setTimeout(() => resolve(this._loadData(bounds)), 100);
- });
- }
- else if (!this.props.draw && this.getBounds() && this.elem.leafletElement.getZoom() >= window.CONFIG.data_min_zoom) {
- let bbox = bounds || this.getBounds();
- // Only load data if bbox is valid and not in an already downloaded area
- if (
- bbox
- && bbox.getSouth() !== bbox.getNorth()
- && bbox.getWest() !== bbox.getEast()
- && (!this.loadedArea || !this.loadedArea.contains(bbox))
- ) {
- // Augment bbox size if too small (to avoid many data reloads)
- while (bbox.getSouthWest().distanceTo(bbox.getNorthEast()) < 400) {
- bbox = bbox.pad(0.1);
- }
- this.loadedArea = bbox;
- this.setState(
- { loading: true },
- async () => {
- try {
- const result = await window.vectorDataManager.loadOSMData(bbox);
- this.setState({ loading: false, dataready: result });
- }
- catch (e) {
- alert("Can't download data from OSM server. Please retry later.");
- this.setState({ loading: false, dataready: false });
- }
- }
- );
- }
- }
- }
- /**
- * Generate layer from given configuration
- * @private
- */
- _getLayer(min, max) {
- const url = "https://webrd04.is.autonavi.com/appmaptile?lang=zh_cn&size=1&scale=1&style=7&x={x}&y={y}&z={z}";
- return <TileLayer
- attribution="© 高德地图"
- url={url}
- key={url} // 'amap'
- minZoom={min}
- maxZoom={max}
- maxNativeZoom={18} // 瓦片源即高德地图可用最大缩放数
- />;
- }
- /**
- * Converts floor imagery info into a Leaflet layer.
- * @private
- */
- _getFloorMapLayer(floormap) {
- if (!floormap || !floormap.topleft) {
- return null;
- }
- else {
- return <FloorImagery
- data={floormap}
- key={floormap.id}
- opacity={floormap.opacity !== undefined && !isNaN(parseFloat(floormap.opacity)) ? floormap.opacity : 1}
- ref={"floormap_" + floormap.id}
- level={this.props.level}
- mode={this.props.mode}
- tool={this.props.floorImageryMode}
- />;
- }
- }
- render() {
- // const floorImgs = window.imageryManager.getFloorImages();
- let levelsList = null;
- if (this.props.mode === Body.MODE_EXPLORE) {
- levelsList = window.vectorDataManager.getAllLevels(true);
- }
- else if (this.props.mode === Body.MODE_BUILDING) {
- if (this.props.building) {
- levelsList = this.props.building.properties.own.levels.slice(0);
- levelsList.sort();
- }
- else {
- levelsList = window.vectorDataManager.getAllLevels(false);
- }
- }
- else if ([Body.MODE_LEVELS, Body.MODE_FEATURES].includes(this.props.mode) && this.props.building) {
- levelsList = this.props.building.properties.own.levels.slice(0);
- levelsList.sort();
- }
- return <div className="app-map-container">
- {(this.props.mode === Body.MODE_CHANGESET || this.state.loading) &&
- <div style={{
- zIndex: 20000,
- background: "rgba(0,0,0,0.5)",
- position: "absolute",
- top: 0, right: 0, left: 0, bottom: 0,
- textAlign: "center", display: "flex", alignItems: "center"
- }}>
- {this.state.loading &&
- <Spinner
- animation="grow"
- variant="light"
- size="lg"
- style={{ margin: "auto", width: "5rem", height: "5rem" }}
- />
- }
- </div>
- }
- <Map
- center={this.props.center}
- zoom={this.props.zoom}
- minZoom={this.props.minZoom}
- maxZoom={this.props.maxZoom}
- maxBounds={this.props.maxBounds}
- className={"app-map" + (this.props.draw ? " leaflet-clickable" : "")}
- ref={elem => this.elem = elem}
- preferCanvas={false}
- editable={true}
- scrollWheelZoom={true}
- doubleClickZoom={this.props.mode === Body.MODE_EXPLORE}
- attributionControl={false}
- boxSelector={false}
- boxZoom={false}
- >
- <AttributionControl
- prefix={window.MAP_NAME + " v" + PACKAGE.version}
- />
- <ScaleControl
- position="bottomleft"
- imperial={false}
- />
- <NorthPointer
- position="bottomright"
- />
- {[Body.MODE_EXPLORE, Body.MODE_BUILDING, Body.MODE_LEVELS, Body.MODE_FEATURES].includes(this.props.mode) && !this.state.loading && this.state.dataready && levelsList &&
- <LevelSelector
- position="topright"
- levels={levelsList}
- level={this.props.level}
- />
- }
- {this._getLayer(this.props.minZoom, this.props.maxZoom)}
- {/* {this.props.selectedBaseImagery && this._getLayer(this.props.selectedBaseImagery, this.props.baseImageryOpacity)} */}
- {/* {this.props.selectedOverlaysImagery && this.props.selectedOverlaysImagery.map(ol => this._getLayer(ol, this.props.overlaysImageryOpacity))} */}
- {/* {this.props.mode !== Body.MODE_EXPLORE && floorImgs && floorImgs.map(fi => this._getFloorMapLayer(fi))} */}
- {/* {!this.state.loading && this.state.dataready && [Body.MODE_BUILDING, Body.MODE_FLOOR_IMAGERY].includes(this.props.mode) &&
- <Building
- styler={this.mapStyler}
- building={this.props.building}
- draw={this.props.draw}
- center={this.props.level}
- locked={this.props.mode === Body.MODE_FLOOR_IMAGERY}
- />
- } */}
- {/* {!this.state.loading && this.state.dataready && this.props.mode === Body.MODE_LEVELS && this.props.building &&
- <Levels
- styler={this.mapStyler}
- level={this.props.level}
- building={this.props.building}
- floor={this.props.floor}
- draw={this.props.draw}
- />
- } */}
- {!this.state.loading && this.state.dataready && (this.props.mode === Body.MODE_EXPLORE || (this.props.mode === Body.MODE_FEATURES && this.props.building)) &&
- <Features
- styler={this.mapStyler}
- level={this.props.level}
- building={this.props.building}
- feature={this.props.feature}
- draw={this.props.draw}
- locked={this.props.mode === Body.MODE_EXPLORE}
- />
- }
- {!this.state.loading && this.state.dataready &&
- <MoveableObject
- styler={this.mapStyler}
- level={this.props.level}
- floor={this.props.floor}
- />
- }
- {!this.state.loading && this.state.dataready &&
- <POIObject
- styler={this.mapStyler}
- level={this.props.level}
- floor={this.props.floor}
- />
- }
- {this.state.initHisTrack &&
- <HistoryTrack
- map={this.elem}
- />
- }
- </Map>
- </div>;
- }
- /**
- * @private
- */
- _followMouse(e) {
- this._mouseCoords = e.latlng;
- // console.log(e.latlng)
- }
- /**
- * @private
- */
- _MouseClick(e) {
- this._mouseCoords = e.latlng;
- // console.log(e.latlng)
- }
- componentDidMount() {
- setTimeout(() => {
- this.invalidateSize();
- this._loadData();
- }, 500);
- MapUtil.map = this.elem.leafletElement;
- // URL hash for map
- this._mapHash = new L.Hash(this.elem.leafletElement);
- // If no valid hash found, use default coordinates from config file or stored cookie
- if (!window.location.hash || !window.location.hash.match(/^#\d+\/-?\d+(.\d+)?\/-?\d+(.\d+)?(\/(-?\d+(.\d+)?)?)?$/)) {
- // Has cookie ?
- const cookieHash = document.cookie.replace(/(?:(?:^|.*;\s*)lasthash\s*=\s*([^;]*).*$)|^.*$/, "$1");
- let newHash;
- if (cookieHash && L.Hash.parseHash(cookieHash)) {
- newHash = cookieHash;
- }
- else {
- newHash = "#" + window.CONFIG.map_initial_zoom + "/" + window.CONFIG.map_initial_latlng.join("/");
- }
- window.history.pushState({}, "", window.location.href.split("#")[0] + newHash);
- }
- L.DomEvent.addListener(window, "hashchange", () => {
- document.cookie = "lasthash=" + window.location.hash;
- });
- this.elem.leafletElement.on("dblclick", e => {
- if (!this.props.draw && this.props.mode !== Body.MODE_EXPLORE) {
- PubSub.publish("body.unselect.feature");
- }
- });
- this.elem.leafletElement.on("zoomend moveend", () => {
- if (this.elem && this.elem.leafletElement) {
- this._loadData();
- const zoom = this.elem.leafletElement.getZoom();
- if (zoom < window.CONFIG.data_min_zoom && (!this._lastZoom || this._lastZoom >= window.CONFIG.data_min_zoom)) {
- this.elem.container.classList.add("app-map-novector");
- PubSub.publishSync("body.unselect.feature");
- // PubSub.publish("body.mode.set", { mode: Body.MODE_BUILDING });
- }
- else if (zoom >= window.CONFIG.data_min_zoom && (!this._lastZoom || this._lastZoom < window.CONFIG.data_min_zoom)) {
- this.elem.container.classList.remove("app-map-novector");
- }
- this._lastZoom = zoom;
- }
- });
- // Follow mouse position
- this.elem.leafletElement.on("mousemove", this._followMouse, this);
- // Mouse Click position
- this.elem.leafletElement.on("click", this._MouseClick, this);
- /**
- * Event for map zoom changes
- * @event map.zoom.changed
- * @memberof MyMap
- * @property {int} zoom The new zoom level
- */
- const alertZoom = () => {
- if (this.elem && this.elem.leafletElement) {
- PubSub.publish("map.zoom.changed", { zoom: this.elem.leafletElement.getZoom() });
- }
- };
- this.elem.leafletElement.on("zoomend", alertZoom);
- alertZoom();
- /**
- * Event for changing current map position
- * @event map.position.set
- * @memberof MyMap
- * @property {LatLng} coordinates The new position
- * @property {int} [zoom] The zoom level
- */
- PubSub.subscribe("map.position.set", (msg, data) => {
- if (data.bbox) {
- const [minlat, maxlat, minlon, maxlon] = data.bbox;
- this.elem.leafletElement.fitBounds([[minlat, minlon], [maxlat, maxlon]]);
- }
- else if (data.zoom) {
- this.elem.leafletElement.setView(data.coordinates, data.zoom);
- }
- else {
- this.elem.leafletElement.panTo(data.coordinates);
- }
- });
- PubSub.subscribe("map.his.init", (msg, data) => {
- this.setState({
- initHisTrack: true
- });
- });
- PubSub.subscribe("map.event.bind", (msg, data) => {
- if (data.eventName === 'contextmenu') {
- this.elem.leafletElement.addEventListener(data.eventName, data.callback);
- return;
- }
- this.elem.leafletElement.on(data.eventName, data.callback);
- });
- PubSub.subscribe("map.event.unbind", (msg, data) => {
- if (data.eventName === 'contextmenu') {
- this.elem.leafletElement.removeEventListener(data.eventName, data.callback);
- return;
- }
- if (!data.callback) {
- this.elem.leafletElement.off(data.eventName);
- return;
- }
- this.elem.leafletElement.off(data.eventName, data.callback);
- });
- }
- componentDidUpdate(fromProps) {
- if (this.props.level !== fromProps.level) {
- this._mapHash.setLevel(this.props.level);
- }
- // const floorImgs = window.imageryManager.getFloorImages();
- // Force update of floor imagery after mode change
- if (fromProps.mode !== this.props.mode) {
- this.invalidateSize();
- // floorImgs.forEach(img => {
- // // Check if we have a leaflet layer
- // if (this.refs["floormap_" + img.id]) {
- // this.refs["floormap_" + img.id].forceUpdate();
- // }
- // });
- }
- // Mouse Click position
- this.elem.leafletElement.off("click", this._MouseClick, this);
- this.elem.leafletElement.on("click", this._MouseClick, this);
- // Follow mouse position
- this.elem.leafletElement.off("mousemove", this._followMouse, this);
- this.elem.leafletElement.on("mousemove", this._followMouse, this);
- // Load wider area if necessary
- if (!this.props.draw && !this.state.loading && this.elem.leafletElement.getZoom() > 19) {
- this._loadData(this.getBounds().pad(0.5 * (this.elem.leafletElement.getZoom() - 19)));
- }
- }
- componentWillUnmount() {
- PubSub.unsubscribe("map");
- this.elem.leafletElement.off("click", this._MouseClick, this);
- this.elem.leafletElement.off("mousemove", this._followMouse, this);
- this.elem.leafletElement.off("load");
- this.elem.leafletElement.off("zoomend");
- this.elem.leafletElement.off("moveend");
- this.elem.leafletElement.off("dblclick");
- }
- }
- export default MyMap;
|