import React from 'react'; import '../css/Map.scss'; import 'leaflet/dist/leaflet.css'; import Leaflet from 'leaflet'; import async from 'async'; import '../vendor/leaflet/L.Control.MousePosition.css'; import '../vendor/leaflet/L.Control.MousePosition'; import '../vendor/leaflet/Leaflet.Autolayers/css/leaflet.auto-layers.css'; import '../vendor/leaflet/Leaflet.Autolayers/leaflet-autolayers'; import $ from 'jquery'; import ErrorMessage from './ErrorMessage'; import SwitchModeButton from './SwitchModeButton'; import ShareButton from './ShareButton'; import AssetDownloads from '../classes/AssetDownloads'; import PropTypes from 'prop-types'; import PluginsAPI from '../classes/plugins/API'; class Map extends React.Component { static defaultProps = { maxzoom: 18, minzoom: 0, showBackground: false, opacity: 100, mapType: "orthophoto", public: false }; static propTypes = { maxzoom: PropTypes.number, minzoom: PropTypes.number, showBackground: PropTypes.bool, tiles: PropTypes.array.isRequired, opacity: PropTypes.number, mapType: PropTypes.oneOf(['orthophoto', 'dsm', 'dtm']), public: PropTypes.bool }; constructor(props) { super(props); this.state = { error: "", singleTask: null // When this is set to a task, show a switch mode button to view the 3d model }; this.imageryLayers = []; this.basemaps = {}; this.mapBounds = null; this.autolayers = null; this.loadImageryLayers = this.loadImageryLayers.bind(this); this.updatePopupFor = this.updatePopupFor.bind(this); this.handleMapMouseDown = this.handleMapMouseDown.bind(this); } updatePopupFor(layer){ const popup = layer.getPopup(); $('#layerOpacity', popup.getContent()).val(layer.options.opacity); } loadImageryLayers(forceAddLayers = false){ const { tiles } = this.props, assets = AssetDownloads.excludeSeparators(), layerId = layer => { const meta = layer[Symbol.for("meta")]; return meta.task.project + "_" + meta.task.id; }; // Remove all previous imagery layers // and keep track of which ones were selected const prevSelectedLayers = []; this.imageryLayers.forEach(layer => { this.autolayers.removeLayer(layer); if (this.map.hasLayer(layer)) prevSelectedLayers.push(layerId(layer)); layer.remove(); }); this.imageryLayers = []; // Request new tiles return new Promise((resolve, reject) => { this.tileJsonRequests = []; async.each(tiles, (tile, done) => { const { url, meta } = tile; this.tileJsonRequests.push($.getJSON(url) .done(info => { const bounds = Leaflet.latLngBounds( [info.bounds.slice(0, 2).reverse(), info.bounds.slice(2, 4).reverse()] ); const layer = Leaflet.tileLayer(info.tiles[0], { bounds, minZoom: info.minzoom, maxZoom: info.maxzoom, tms: info.scheme === 'tms', opacity: this.props.opacity / 100 }); // Associate metadata with this layer meta.name = info.name; layer[Symbol.for("meta")] = meta; if (forceAddLayers || prevSelectedLayers.indexOf(layerId(layer)) !== -1){ layer.addTo(this.map); } // Show 3D switch button only if we have a single orthophoto if (tiles.length === 1){ this.setState({singleTask: meta.task}); } // For some reason, getLatLng is not defined for tileLayer? // We need this function if other code calls layer.openPopup() layer.getLatLng = function(){ return this.options.bounds.getCenter(); }; var popup = L.DomUtil.create('div', 'infoWindow'); popup.innerHTML = `
${info.name}
Bounds: [${layer.options.bounds.toBBoxString().split(",").join(", ")}]
`; layer.bindPopup(popup); $('#layerOpacity', popup).on('change input', function() { layer.setOpacity($('#layerOpacity', popup).val()); }); this.imageryLayers.push(layer); let mapBounds = this.mapBounds || Leaflet.latLngBounds(); mapBounds.extend(bounds); this.mapBounds = mapBounds; // Add layer to layers control this.autolayers.addOverlay(layer, info.name); done(); }) .fail((_, __, err) => done(err)) ); }, err => { if (err){ this.setState({error: err.message || JSON.stringify(err)}); reject(err); }else{ resolve(); } }); }); } componentDidMount() { const { showBackground } = this.props; this.map = Leaflet.map(this.container, { scrollWheelZoom: true, positionControl: true, zoomControl: false }); PluginsAPI.Map.triggerWillAddControls({ map: this.map }); Leaflet.control.scale({ maxWidth: 250, }).addTo(this.map); //add zoom control with your options Leaflet.control.zoom({ position:'bottomleft' }).addTo(this.map); if (showBackground) { this.basemaps = { "Google Maps Hybrid": L.tileLayer('//{s}.google.com/vt/lyrs=s,h&x={x}&y={y}&z={z}', { attribution: 'Map data: © Google Maps', subdomains: ['mt0','mt1','mt2','mt3'], maxZoom: 21, minZoom: 0, label: 'Google Maps Hybrid' }).addTo(this.map), "ESRI Satellite": L.tileLayer('//server.arcgisonline.com/ArcGIS/rest/services/World_Imagery/MapServer/tile/{z}/{y}/{x}', { attribution: 'Tiles © Esri — Source: Esri, i-cubed, USDA, USGS, AEX, GeoEye, Getmapping, Aerogrid, IGN, IGP, UPR-EGP, and the GIS User Community', maxZoom: 21, minZoom: 0, label: 'ESRI Satellite' // optional label used for tooltip }), "OSM Mapnik": L.tileLayer('//{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', { attribution: '© OpenStreetMap', maxZoom: 21, minZoom: 0, label: 'OSM Mapnik' // optional label used for tooltip }) }; } this.autolayers = Leaflet.control.autolayers({ overlays: {}, selectedOverlays: [], baseLayers: this.basemaps }).addTo(this.map); this.map.fitWorld(); this.map.attributionControl.setPrefix(""); this.loadImageryLayers(true).then(() => { this.map.fitBounds(this.mapBounds); this.map.on('click', e => { // Find first tile layer at the selected coordinates for (let layer of this.imageryLayers){ if (layer._map && layer.options.bounds.contains(e.latlng)){ this.updatePopupFor(layer); layer.openPopup(); break; } } }); }); // PluginsAPI.events.addListener('Map::AddPanel', (e) => { // console.log("Received response: " + e); // }); PluginsAPI.Map.triggerDidAddControls({ map: this.map }); } componentDidUpdate(prevProps) { this.imageryLayers.forEach(imageryLayer => { imageryLayer.setOpacity(this.props.opacity / 100); this.updatePopupFor(imageryLayer); }); if (prevProps.tiles !== this.props.tiles){ this.loadImageryLayers().then(() => { // console.log("GOT: ", this.autolayers, this.autolayers.selectedOverlays); }); } } componentWillUnmount() { this.map.remove(); if (this.tileJsonRequests) { this.tileJsonRequests.forEach(tileJsonRequest => this.tileJsonRequest.abort()); this.tileJsonRequests = []; } } handleMapMouseDown(e){ // Make sure the share popup closes if (this.sharePopup) this.shareButton.hidePopup(); } render() { return (
(this.container = domNode)} onMouseDown={this.handleMapMouseDown} >
{(!this.props.public && this.state.singleTask !== null) ? { this.shareButton = ref; }} task={this.state.singleTask} linksTarget="map" /> : ""}
); } } export default Map;