2016-11-09 21:13:43 +00:00
|
|
|
import React from 'react';
|
|
|
|
import '../css/Map.scss';
|
2016-11-11 16:00:31 +00:00
|
|
|
import 'leaflet/dist/leaflet.css';
|
|
|
|
import Leaflet from 'leaflet';
|
2016-11-26 15:27:54 +00:00
|
|
|
import async from 'async';
|
2016-11-17 23:51:07 +00:00
|
|
|
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';
|
2016-11-09 21:13:43 +00:00
|
|
|
import $ from 'jquery';
|
|
|
|
import ErrorMessage from './ErrorMessage';
|
2017-01-21 15:49:30 +00:00
|
|
|
import SwitchModeButton from './SwitchModeButton';
|
2017-12-01 18:52:36 +00:00
|
|
|
import ShareButton from './ShareButton';
|
2016-11-17 23:51:07 +00:00
|
|
|
import AssetDownloads from '../classes/AssetDownloads';
|
2017-07-25 18:09:51 +00:00
|
|
|
import PropTypes from 'prop-types';
|
2018-02-09 17:38:42 +00:00
|
|
|
import PluginsAPI from '../classes/plugins/API';
|
2016-11-09 21:13:43 +00:00
|
|
|
|
|
|
|
class Map extends React.Component {
|
|
|
|
static defaultProps = {
|
|
|
|
maxzoom: 18,
|
|
|
|
minzoom: 0,
|
|
|
|
showBackground: false,
|
2017-07-12 17:35:28 +00:00
|
|
|
opacity: 100,
|
2017-12-03 22:56:30 +00:00
|
|
|
mapType: "orthophoto",
|
|
|
|
public: false
|
2016-11-16 18:02:43 +00:00
|
|
|
};
|
2016-11-09 21:13:43 +00:00
|
|
|
|
2016-11-14 21:32:05 +00:00
|
|
|
static propTypes = {
|
2017-07-25 18:09:51 +00:00
|
|
|
maxzoom: PropTypes.number,
|
|
|
|
minzoom: PropTypes.number,
|
|
|
|
showBackground: PropTypes.bool,
|
|
|
|
tiles: PropTypes.array.isRequired,
|
|
|
|
opacity: PropTypes.number,
|
2017-12-03 22:56:30 +00:00
|
|
|
mapType: PropTypes.oneOf(['orthophoto', 'dsm', 'dtm']),
|
|
|
|
public: PropTypes.bool
|
2016-11-16 18:02:43 +00:00
|
|
|
};
|
2016-11-09 21:13:43 +00:00
|
|
|
|
|
|
|
constructor(props) {
|
|
|
|
super(props);
|
|
|
|
|
|
|
|
this.state = {
|
2017-01-21 15:49:30 +00:00
|
|
|
error: "",
|
2017-12-01 18:52:36 +00:00
|
|
|
singleTask: null // When this is set to a task, show a switch mode button to view the 3d model
|
2016-11-09 21:13:43 +00:00
|
|
|
};
|
2016-11-16 18:02:43 +00:00
|
|
|
|
|
|
|
this.imageryLayers = [];
|
2016-11-17 23:51:07 +00:00
|
|
|
this.basemaps = {};
|
|
|
|
this.mapBounds = null;
|
2017-07-13 21:06:25 +00:00
|
|
|
this.autolayers = null;
|
2017-07-12 20:51:25 +00:00
|
|
|
|
|
|
|
this.loadImageryLayers = this.loadImageryLayers.bind(this);
|
2017-08-31 21:22:18 +00:00
|
|
|
this.updatePopupFor = this.updatePopupFor.bind(this);
|
2017-12-03 18:37:13 +00:00
|
|
|
this.handleMapMouseDown = this.handleMapMouseDown.bind(this);
|
2017-08-31 21:22:18 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
updatePopupFor(layer){
|
|
|
|
const popup = layer.getPopup();
|
|
|
|
$('#layerOpacity', popup.getContent()).val(layer.options.opacity);
|
2017-07-12 20:51:25 +00:00
|
|
|
}
|
|
|
|
|
2017-07-13 21:06:25 +00:00
|
|
|
loadImageryLayers(forceAddLayers = false){
|
2017-07-12 20:51:25 +00:00
|
|
|
const { tiles } = this.props,
|
2017-07-13 21:06:25 +00:00
|
|
|
assets = AssetDownloads.excludeSeparators(),
|
|
|
|
layerId = layer => {
|
|
|
|
const meta = layer[Symbol.for("meta")];
|
2017-12-01 18:52:36 +00:00
|
|
|
return meta.task.project + "_" + meta.task.id;
|
2017-07-13 21:06:25 +00:00
|
|
|
};
|
2017-07-12 20:51:25 +00:00
|
|
|
|
|
|
|
// Remove all previous imagery layers
|
2017-07-13 21:06:25 +00:00
|
|
|
// 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();
|
|
|
|
});
|
2017-07-12 20:51:25 +00:00
|
|
|
this.imageryLayers = [];
|
|
|
|
|
2017-07-13 21:06:25 +00:00
|
|
|
// Request new tiles
|
2017-07-12 20:51:25 +00:00
|
|
|
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
|
2017-07-13 21:06:25 +00:00
|
|
|
});
|
|
|
|
|
2017-07-12 20:51:25 +00:00
|
|
|
// Associate metadata with this layer
|
|
|
|
meta.name = info.name;
|
|
|
|
layer[Symbol.for("meta")] = meta;
|
|
|
|
|
2017-07-13 21:06:25 +00:00
|
|
|
if (forceAddLayers || prevSelectedLayers.indexOf(layerId(layer)) !== -1){
|
|
|
|
layer.addTo(this.map);
|
|
|
|
}
|
|
|
|
|
2017-07-12 20:51:25 +00:00
|
|
|
// Show 3D switch button only if we have a single orthophoto
|
|
|
|
if (tiles.length === 1){
|
2017-12-01 18:52:36 +00:00
|
|
|
this.setState({singleTask: meta.task});
|
2017-07-12 20:51:25 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// 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();
|
|
|
|
};
|
|
|
|
|
2017-08-08 18:24:16 +00:00
|
|
|
var popup = L.DomUtil.create('div', 'infoWindow');
|
|
|
|
|
|
|
|
popup.innerHTML = `<div class="title">
|
|
|
|
${info.name}
|
|
|
|
</div>
|
2017-08-31 21:22:18 +00:00
|
|
|
<div class="popup-opacity-slider">Opacity: <input id="layerOpacity" type="range" value="${layer.options.opacity}" min="0" max="1" step="0.01" /></div>
|
2017-08-08 18:24:16 +00:00
|
|
|
<div>Bounds: [${layer.options.bounds.toBBoxString().split(",").join(", ")}]</div>
|
|
|
|
<ul class="asset-links">
|
|
|
|
${assets.map(asset => {
|
2017-12-01 18:52:36 +00:00
|
|
|
return `<li><a href="${asset.downloadUrl(meta.task.project, meta.task.id)}">${asset.label}</a></li>`;
|
2017-08-08 18:24:16 +00:00
|
|
|
}).join("")}
|
|
|
|
</ul>
|
|
|
|
|
|
|
|
<button
|
2017-12-01 18:52:36 +00:00
|
|
|
onclick="location.href='/3d/project/${meta.task.project}/task/${meta.task.id}/';"
|
2017-08-08 18:24:16 +00:00
|
|
|
type="button"
|
2017-11-24 20:20:38 +00:00
|
|
|
class="switchModeButton btn btn-sm btn-secondary">
|
2017-08-08 18:24:16 +00:00
|
|
|
<i class="fa fa-cube"></i> 3D
|
|
|
|
</button>`;
|
|
|
|
|
2018-03-27 18:35:16 +00:00
|
|
|
layer.bindPopup(popup);
|
2017-08-08 18:24:16 +00:00
|
|
|
|
2017-08-31 21:22:18 +00:00
|
|
|
$('#layerOpacity', popup).on('change input', function() {
|
|
|
|
layer.setOpacity($('#layerOpacity', popup).val());
|
2017-08-08 18:24:16 +00:00
|
|
|
});
|
2017-07-12 20:51:25 +00:00
|
|
|
|
|
|
|
this.imageryLayers.push(layer);
|
|
|
|
|
|
|
|
let mapBounds = this.mapBounds || Leaflet.latLngBounds();
|
|
|
|
mapBounds.extend(bounds);
|
|
|
|
this.mapBounds = mapBounds;
|
|
|
|
|
2017-07-13 21:06:25 +00:00
|
|
|
// Add layer to layers control
|
|
|
|
this.autolayers.addOverlay(layer, info.name);
|
|
|
|
|
2017-07-12 20:51:25 +00:00
|
|
|
done();
|
|
|
|
})
|
|
|
|
.fail((_, __, err) => done(err))
|
|
|
|
);
|
|
|
|
}, err => {
|
|
|
|
if (err){
|
|
|
|
this.setState({error: err.message || JSON.stringify(err)});
|
|
|
|
reject(err);
|
|
|
|
}else{
|
|
|
|
resolve();
|
|
|
|
}
|
|
|
|
});
|
|
|
|
});
|
2016-11-09 21:13:43 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
componentDidMount() {
|
2017-07-12 20:51:25 +00:00
|
|
|
const { showBackground } = this.props;
|
2016-11-09 21:13:43 +00:00
|
|
|
|
2016-11-17 23:51:07 +00:00
|
|
|
this.map = Leaflet.map(this.container, {
|
|
|
|
scrollWheelZoom: true,
|
2018-02-09 17:38:42 +00:00
|
|
|
positionControl: true,
|
|
|
|
zoomControl: false
|
2016-11-09 21:13:43 +00:00
|
|
|
});
|
|
|
|
|
2018-02-09 17:38:42 +00:00
|
|
|
PluginsAPI.Map.triggerWillAddControls({
|
|
|
|
map: this.map
|
2017-07-13 21:06:25 +00:00
|
|
|
});
|
2018-02-09 17:38:42 +00:00
|
|
|
|
|
|
|
Leaflet.control.scale({
|
|
|
|
maxWidth: 250,
|
|
|
|
}).addTo(this.map);
|
|
|
|
|
|
|
|
//add zoom control with your options
|
|
|
|
Leaflet.control.zoom({
|
|
|
|
position:'bottomleft'
|
|
|
|
}).addTo(this.map);
|
2017-07-13 21:06:25 +00:00
|
|
|
|
2016-11-11 16:00:31 +00:00
|
|
|
if (showBackground) {
|
2016-11-17 23:51:07 +00:00
|
|
|
this.basemaps = {
|
|
|
|
"Google Maps Hybrid": L.tileLayer('//{s}.google.com/vt/lyrs=s,h&x={x}&y={y}&z={z}', {
|
2016-11-11 17:55:56 +00:00
|
|
|
attribution: 'Map data: © Google Maps',
|
|
|
|
subdomains: ['mt0','mt1','mt2','mt3'],
|
2017-01-19 20:40:04 +00:00
|
|
|
maxZoom: 21,
|
2016-11-11 17:55:56 +00:00
|
|
|
minZoom: 0,
|
|
|
|
label: 'Google Maps Hybrid'
|
2016-11-17 23:51:07 +00:00
|
|
|
}).addTo(this.map),
|
|
|
|
"ESRI Satellite": L.tileLayer('//server.arcgisonline.com/ArcGIS/rest/services/World_Imagery/MapServer/tile/{z}/{y}/{x}', {
|
2016-11-11 16:00:31 +00:00
|
|
|
attribution: 'Tiles © Esri — Source: Esri, i-cubed, USDA, USGS, AEX, GeoEye, Getmapping, Aerogrid, IGN, IGP, UPR-EGP, and the GIS User Community',
|
2017-01-19 20:40:04 +00:00
|
|
|
maxZoom: 21,
|
2016-11-11 16:00:31 +00:00
|
|
|
minZoom: 0,
|
|
|
|
label: 'ESRI Satellite' // optional label used for tooltip
|
|
|
|
}),
|
2016-11-17 23:51:07 +00:00
|
|
|
"OSM Mapnik": L.tileLayer('//{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
|
2016-11-11 16:00:31 +00:00
|
|
|
attribution: '© <a href="http://www.openstreetmap.org/copyright">OpenStreetMap</a>',
|
2017-01-19 20:40:04 +00:00
|
|
|
maxZoom: 21,
|
2016-11-11 16:00:31 +00:00
|
|
|
minZoom: 0,
|
|
|
|
label: 'OSM Mapnik' // optional label used for tooltip
|
|
|
|
})
|
2016-11-17 23:51:07 +00:00
|
|
|
};
|
2016-11-11 16:00:31 +00:00
|
|
|
}
|
|
|
|
|
2017-07-13 21:06:25 +00:00
|
|
|
this.autolayers = Leaflet.control.autolayers({
|
|
|
|
overlays: {},
|
|
|
|
selectedOverlays: [],
|
|
|
|
baseLayers: this.basemaps
|
|
|
|
}).addTo(this.map);
|
|
|
|
|
2016-11-17 23:51:07 +00:00
|
|
|
this.map.fitWorld();
|
|
|
|
this.map.attributionControl.setPrefix("");
|
2016-11-09 21:13:43 +00:00
|
|
|
|
2017-07-13 21:06:25 +00:00
|
|
|
this.loadImageryLayers(true).then(() => {
|
2016-11-21 21:32:37 +00:00
|
|
|
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)){
|
2017-08-31 21:22:18 +00:00
|
|
|
this.updatePopupFor(layer);
|
2016-11-21 21:32:37 +00:00
|
|
|
layer.openPopup();
|
|
|
|
break;
|
2016-11-17 23:51:07 +00:00
|
|
|
}
|
2016-11-21 21:32:37 +00:00
|
|
|
}
|
|
|
|
});
|
2016-11-16 18:02:43 +00:00
|
|
|
});
|
2018-01-30 22:41:32 +00:00
|
|
|
|
|
|
|
// PluginsAPI.events.addListener('Map::AddPanel', (e) => {
|
|
|
|
// console.log("Received response: " + e);
|
|
|
|
// });
|
2018-02-09 17:38:42 +00:00
|
|
|
PluginsAPI.Map.triggerDidAddControls({
|
2018-01-30 22:41:32 +00:00
|
|
|
map: this.map
|
|
|
|
});
|
2016-11-16 18:02:43 +00:00
|
|
|
}
|
|
|
|
|
2017-07-12 20:51:25 +00:00
|
|
|
componentDidUpdate(prevProps) {
|
2016-11-16 18:02:43 +00:00
|
|
|
this.imageryLayers.forEach(imageryLayer => {
|
|
|
|
imageryLayer.setOpacity(this.props.opacity / 100);
|
2017-08-31 21:22:18 +00:00
|
|
|
this.updatePopupFor(imageryLayer);
|
2016-11-16 18:02:43 +00:00
|
|
|
});
|
2017-07-12 20:51:25 +00:00
|
|
|
|
|
|
|
if (prevProps.tiles !== this.props.tiles){
|
2017-07-13 21:06:25 +00:00
|
|
|
this.loadImageryLayers().then(() => {
|
|
|
|
// console.log("GOT: ", this.autolayers, this.autolayers.selectedOverlays);
|
|
|
|
});
|
2017-07-12 20:51:25 +00:00
|
|
|
}
|
2016-11-09 21:13:43 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
componentWillUnmount() {
|
2016-11-17 23:51:07 +00:00
|
|
|
this.map.remove();
|
2016-11-16 18:02:43 +00:00
|
|
|
|
|
|
|
if (this.tileJsonRequests) {
|
|
|
|
this.tileJsonRequests.forEach(tileJsonRequest => this.tileJsonRequest.abort());
|
|
|
|
this.tileJsonRequests = [];
|
|
|
|
}
|
2016-11-09 21:13:43 +00:00
|
|
|
}
|
|
|
|
|
2017-12-03 18:37:13 +00:00
|
|
|
handleMapMouseDown(e){
|
|
|
|
// Make sure the share popup closes
|
2018-04-29 19:16:34 +00:00
|
|
|
if (this.shareButton) this.shareButton.hidePopup();
|
2017-12-03 18:37:13 +00:00
|
|
|
}
|
|
|
|
|
2016-11-09 21:13:43 +00:00
|
|
|
render() {
|
|
|
|
return (
|
2016-11-17 23:51:07 +00:00
|
|
|
<div style={{height: "100%"}} className="map">
|
2016-11-14 15:58:00 +00:00
|
|
|
<ErrorMessage bind={[this, 'error']} />
|
2018-02-09 17:38:42 +00:00
|
|
|
|
2016-11-09 21:13:43 +00:00
|
|
|
<div
|
|
|
|
style={{height: "100%"}}
|
2017-12-03 18:37:13 +00:00
|
|
|
ref={(domNode) => (this.container = domNode)}
|
|
|
|
onMouseDown={this.handleMapMouseDown}
|
|
|
|
>
|
|
|
|
</div>
|
|
|
|
|
2017-12-01 18:52:36 +00:00
|
|
|
|
2017-12-03 18:37:13 +00:00
|
|
|
<div className="actionButtons">
|
2017-12-03 22:56:30 +00:00
|
|
|
{(!this.props.public && this.state.singleTask !== null) ?
|
2017-12-03 18:37:13 +00:00
|
|
|
<ShareButton
|
|
|
|
ref={(ref) => { this.shareButton = ref; }}
|
|
|
|
task={this.state.singleTask}
|
2017-12-03 22:56:30 +00:00
|
|
|
linksTarget="map"
|
2017-12-03 18:37:13 +00:00
|
|
|
/>
|
2017-12-01 18:52:36 +00:00
|
|
|
: ""}
|
2017-01-21 15:49:30 +00:00
|
|
|
<SwitchModeButton
|
2017-12-01 18:52:36 +00:00
|
|
|
task={this.state.singleTask}
|
2017-12-03 22:56:30 +00:00
|
|
|
type="mapToModel"
|
|
|
|
public={this.props.public} />
|
2017-01-21 15:49:30 +00:00
|
|
|
</div>
|
2016-11-09 21:13:43 +00:00
|
|
|
</div>
|
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-11-24 20:20:38 +00:00
|
|
|
export default Map;
|