kopia lustrzana https://github.com/OpenDroneMap/WebODM
Layer list fix, api docs update, unit test fix, map type button switch improvements
rodzic
f7fa29cd3c
commit
ab0b91a77e
|
@ -31,8 +31,8 @@ class MapView extends React.Component {
|
||||||
}
|
}
|
||||||
|
|
||||||
getTilesByMapType(type){
|
getTilesByMapType(type){
|
||||||
// Go through the list of tiles and return
|
// Go through the list of map items and return
|
||||||
// only those that match a particular type
|
// only those that match a particular type (in tile format)
|
||||||
const tiles = [];
|
const tiles = [];
|
||||||
|
|
||||||
this.props.mapItems.forEach(mapItem => {
|
this.props.mapItems.forEach(mapItem => {
|
||||||
|
@ -64,7 +64,7 @@ class MapView extends React.Component {
|
||||||
|
|
||||||
render(){
|
render(){
|
||||||
const { opacity } = this.state;
|
const { opacity } = this.state;
|
||||||
const mapTypeButtons = [
|
let mapTypeButtons = [
|
||||||
{
|
{
|
||||||
label: "Orthophoto",
|
label: "Orthophoto",
|
||||||
type: "orthophoto"
|
type: "orthophoto"
|
||||||
|
@ -77,7 +77,10 @@ class MapView extends React.Component {
|
||||||
label: "Terrain Model",
|
label: "Terrain Model",
|
||||||
type: "dtm"
|
type: "dtm"
|
||||||
}
|
}
|
||||||
];
|
].filter(mapType => this.getTilesByMapType(mapType.type).length > 0 );
|
||||||
|
|
||||||
|
// If we have only one button, hide it...
|
||||||
|
if (mapTypeButtons.length === 1) mapTypeButtons = [];
|
||||||
|
|
||||||
return (<div className="map-view">
|
return (<div className="map-view">
|
||||||
<div className="map-type-selector btn-group" role="group">
|
<div className="map-type-selector btn-group" role="group">
|
||||||
|
|
|
@ -45,18 +45,31 @@ class Map extends React.Component {
|
||||||
this.imageryLayers = [];
|
this.imageryLayers = [];
|
||||||
this.basemaps = {};
|
this.basemaps = {};
|
||||||
this.mapBounds = null;
|
this.mapBounds = null;
|
||||||
|
this.autolayers = null;
|
||||||
|
|
||||||
this.loadImageryLayers = this.loadImageryLayers.bind(this);
|
this.loadImageryLayers = this.loadImageryLayers.bind(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
loadImageryLayers(){
|
loadImageryLayers(forceAddLayers = false){
|
||||||
const { tiles } = this.props,
|
const { tiles } = this.props,
|
||||||
assets = AssetDownloads.excludeSeparators();
|
assets = AssetDownloads.excludeSeparators(),
|
||||||
|
layerId = layer => {
|
||||||
|
const meta = layer[Symbol.for("meta")];
|
||||||
|
return meta.project + "_" + meta.task;
|
||||||
|
};
|
||||||
|
|
||||||
// Remove all previous imagery layers
|
// Remove all previous imagery layers
|
||||||
this.imageryLayers.forEach(layer => layer.remove());
|
// 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 = [];
|
this.imageryLayers = [];
|
||||||
|
|
||||||
|
// Request new tiles
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
this.tileJsonRequests = [];
|
this.tileJsonRequests = [];
|
||||||
|
|
||||||
|
@ -74,12 +87,16 @@ class Map extends React.Component {
|
||||||
maxZoom: info.maxzoom,
|
maxZoom: info.maxzoom,
|
||||||
tms: info.scheme === 'tms',
|
tms: info.scheme === 'tms',
|
||||||
opacity: this.props.opacity / 100
|
opacity: this.props.opacity / 100
|
||||||
}).addTo(this.map);
|
});
|
||||||
|
|
||||||
// Associate metadata with this layer
|
// Associate metadata with this layer
|
||||||
meta.name = info.name;
|
meta.name = info.name;
|
||||||
layer[Symbol.for("meta")] = meta;
|
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
|
// Show 3D switch button only if we have a single orthophoto
|
||||||
const task = {
|
const task = {
|
||||||
id: meta.task,
|
id: meta.task,
|
||||||
|
@ -118,6 +135,9 @@ class Map extends React.Component {
|
||||||
mapBounds.extend(bounds);
|
mapBounds.extend(bounds);
|
||||||
this.mapBounds = mapBounds;
|
this.mapBounds = mapBounds;
|
||||||
|
|
||||||
|
// Add layer to layers control
|
||||||
|
this.autolayers.addOverlay(layer, info.name);
|
||||||
|
|
||||||
done();
|
done();
|
||||||
})
|
})
|
||||||
.fail((_, __, err) => done(err))
|
.fail((_, __, err) => done(err))
|
||||||
|
@ -138,10 +158,17 @@ class Map extends React.Component {
|
||||||
|
|
||||||
this.map = Leaflet.map(this.container, {
|
this.map = Leaflet.map(this.container, {
|
||||||
scrollWheelZoom: true,
|
scrollWheelZoom: true,
|
||||||
measureControl: true,
|
|
||||||
positionControl: true
|
positionControl: true
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const measureControl = Leaflet.control.measure({
|
||||||
|
primaryLengthUnit: 'meters',
|
||||||
|
secondaryLengthUnit: 'feet',
|
||||||
|
primaryAreaUnit: 'sqmeters',
|
||||||
|
secondaryAreaUnit: 'acres'
|
||||||
|
});
|
||||||
|
measureControl.addTo(this.map);
|
||||||
|
|
||||||
if (showBackground) {
|
if (showBackground) {
|
||||||
this.basemaps = {
|
this.basemaps = {
|
||||||
"Google Maps Hybrid": L.tileLayer('//{s}.google.com/vt/lyrs=s,h&x={x}&y={y}&z={z}', {
|
"Google Maps Hybrid": L.tileLayer('//{s}.google.com/vt/lyrs=s,h&x={x}&y={y}&z={z}', {
|
||||||
|
@ -166,6 +193,12 @@ class Map extends React.Component {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this.autolayers = Leaflet.control.autolayers({
|
||||||
|
overlays: {},
|
||||||
|
selectedOverlays: [],
|
||||||
|
baseLayers: this.basemaps
|
||||||
|
}).addTo(this.map);
|
||||||
|
|
||||||
this.map.fitWorld();
|
this.map.fitWorld();
|
||||||
|
|
||||||
Leaflet.control.scale({
|
Leaflet.control.scale({
|
||||||
|
@ -173,22 +206,9 @@ class Map extends React.Component {
|
||||||
}).addTo(this.map);
|
}).addTo(this.map);
|
||||||
this.map.attributionControl.setPrefix("");
|
this.map.attributionControl.setPrefix("");
|
||||||
|
|
||||||
this.loadImageryLayers().then(() => {
|
this.loadImageryLayers(true).then(() => {
|
||||||
this.map.fitBounds(this.mapBounds);
|
this.map.fitBounds(this.mapBounds);
|
||||||
|
|
||||||
// Add basemaps / layers control
|
|
||||||
let overlays = {};
|
|
||||||
this.imageryLayers.forEach(layer => {
|
|
||||||
const meta = layer[Symbol.for("meta")];
|
|
||||||
overlays[meta.name] = layer;
|
|
||||||
});
|
|
||||||
|
|
||||||
Leaflet.control.autolayers({
|
|
||||||
overlays: overlays,
|
|
||||||
selectedOverlays: [],
|
|
||||||
baseLayers: this.basemaps
|
|
||||||
}).addTo(this.map);
|
|
||||||
|
|
||||||
this.map.on('click', e => {
|
this.map.on('click', e => {
|
||||||
// Find first tile layer at the selected coordinates
|
// Find first tile layer at the selected coordinates
|
||||||
for (let layer of this.imageryLayers){
|
for (let layer of this.imageryLayers){
|
||||||
|
@ -199,7 +219,6 @@ class Map extends React.Component {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidUpdate(prevProps) {
|
componentDidUpdate(prevProps) {
|
||||||
|
@ -208,7 +227,9 @@ class Map extends React.Component {
|
||||||
});
|
});
|
||||||
|
|
||||||
if (prevProps.tiles !== this.props.tiles){
|
if (prevProps.tiles !== this.props.tiles){
|
||||||
this.loadImageryLayers();
|
this.loadImageryLayers().then(() => {
|
||||||
|
// console.log("GOT: ", this.autolayers, this.autolayers.selectedOverlays);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -156,20 +156,22 @@ class TestApiTask(BootTransactionTestCase):
|
||||||
self.assertTrue(task.processing_node is None)
|
self.assertTrue(task.processing_node is None)
|
||||||
|
|
||||||
# tiles.json should not be accessible at this point
|
# tiles.json should not be accessible at this point
|
||||||
res = client.get("/api/projects/{}/tasks/{}/tiles.json".format(project.id, task.id))
|
tile_types = ['orthophoto', 'dsm', 'dtm']
|
||||||
self.assertTrue(res.status_code == status.HTTP_400_BAD_REQUEST)
|
for tile_type in tile_types:
|
||||||
|
res = client.get("/api/projects/{}/tasks/{}/{}/tiles.json".format(project.id, task.id, tile_type))
|
||||||
|
self.assertTrue(res.status_code == status.HTTP_400_BAD_REQUEST)
|
||||||
|
|
||||||
# Neither should an individual tile
|
# Neither should an individual tile
|
||||||
# Z/X/Y coords are choosen based on node-odm test dataset for orthophoto_tiles/
|
# Z/X/Y coords are choosen based on node-odm test dataset for orthophoto_tiles/
|
||||||
res = client.get("/api/projects/{}/tasks/{}/tiles/16/16020/42443.png".format(project.id, task.id))
|
res = client.get("/api/projects/{}/tasks/{}/orthophoto/tiles/16/16020/42443.png".format(project.id, task.id))
|
||||||
self.assertTrue(res.status_code == status.HTTP_404_NOT_FOUND)
|
self.assertTrue(res.status_code == status.HTTP_404_NOT_FOUND)
|
||||||
|
|
||||||
# Cannot access a tiles.json we have no access to
|
# Cannot access a tiles.json we have no access to
|
||||||
res = client.get("/api/projects/{}/tasks/{}/tiles.json".format(other_project.id, other_task.id))
|
res = client.get("/api/projects/{}/tasks/{}/orthophoto/tiles.json".format(other_project.id, other_task.id))
|
||||||
self.assertTrue(res.status_code == status.HTTP_404_NOT_FOUND)
|
self.assertTrue(res.status_code == status.HTTP_404_NOT_FOUND)
|
||||||
|
|
||||||
# Cannot access an individual tile we have no access to
|
# Cannot access an individual tile we have no access to
|
||||||
res = client.get("/api/projects/{}/tasks/{}/tiles/16/16020/42443.png".format(other_project.id, other_task.id))
|
res = client.get("/api/projects/{}/tasks/{}/orthophoto/tiles/16/16020/42443.png".format(other_project.id, other_task.id))
|
||||||
self.assertTrue(res.status_code == status.HTTP_404_NOT_FOUND)
|
self.assertTrue(res.status_code == status.HTTP_404_NOT_FOUND)
|
||||||
|
|
||||||
# Cannot download assets (they don't exist yet)
|
# Cannot download assets (they don't exist yet)
|
||||||
|
@ -226,8 +228,9 @@ class TestApiTask(BootTransactionTestCase):
|
||||||
self.assertTrue(res.status_code == status.HTTP_200_OK)
|
self.assertTrue(res.status_code == status.HTTP_200_OK)
|
||||||
|
|
||||||
# Can access tiles.json
|
# Can access tiles.json
|
||||||
res = client.get("/api/projects/{}/tasks/{}/tiles.json".format(project.id, task.id))
|
for tile_type in tile_types:
|
||||||
self.assertTrue(res.status_code == status.HTTP_200_OK)
|
res = client.get("/api/projects/{}/tasks/{}/{}/tiles.json".format(project.id, task.id, tile_type))
|
||||||
|
self.assertTrue(res.status_code == status.HTTP_200_OK)
|
||||||
|
|
||||||
# Bounds are what we expect them to be
|
# Bounds are what we expect them to be
|
||||||
# (4 coords in lat/lon)
|
# (4 coords in lat/lon)
|
||||||
|
@ -236,8 +239,9 @@ class TestApiTask(BootTransactionTestCase):
|
||||||
self.assertTrue(round(tiles['bounds'][0], 7) == -91.9945132)
|
self.assertTrue(round(tiles['bounds'][0], 7) == -91.9945132)
|
||||||
|
|
||||||
# Can access individual tiles
|
# Can access individual tiles
|
||||||
res = client.get("/api/projects/{}/tasks/{}/tiles/16/16020/42443.png".format(project.id, task.id))
|
for tile_type in tile_types:
|
||||||
self.assertTrue(res.status_code == status.HTTP_200_OK)
|
res = client.get("/api/projects/{}/tasks/{}/{}/tiles/16/16020/42443.png".format(project.id, task.id, tile_type))
|
||||||
|
self.assertTrue(res.status_code == status.HTTP_200_OK)
|
||||||
|
|
||||||
# Restart a task
|
# Restart a task
|
||||||
testWatch.clear()
|
testWatch.clear()
|
||||||
|
@ -379,6 +383,20 @@ class TestApiTask(BootTransactionTestCase):
|
||||||
# orthophoto_extent should be none
|
# orthophoto_extent should be none
|
||||||
self.assertTrue(task.orthophoto_extent is None)
|
self.assertTrue(task.orthophoto_extent is None)
|
||||||
|
|
||||||
|
# but other extents should be populated
|
||||||
|
self.assertTrue(task.dsm_extent is not None)
|
||||||
|
self.assertTrue(task.dtm_extent is not None)
|
||||||
|
self.assertTrue(os.path.exists(task.assets_path("dsm_tiles")))
|
||||||
|
self.assertTrue(os.path.exists(task.assets_path("dtm_tiles")))
|
||||||
|
|
||||||
|
# Can access only tiles of available assets
|
||||||
|
res = client.get("/api/projects/{}/tasks/{}/dsm/tiles.json".format(project.id, task.id))
|
||||||
|
self.assertTrue(res.status_code == status.HTTP_200_OK)
|
||||||
|
res = client.get("/api/projects/{}/tasks/{}/dtm/tiles.json".format(project.id, task.id))
|
||||||
|
self.assertTrue(res.status_code == status.HTTP_200_OK)
|
||||||
|
res = client.get("/api/projects/{}/tasks/{}/orthophoto/tiles.json".format(project.id, task.id))
|
||||||
|
self.assertTrue(res.status_code == status.HTTP_400_BAD_REQUEST)
|
||||||
|
|
||||||
# Available assets should be missing orthophoto.tif type
|
# Available assets should be missing orthophoto.tif type
|
||||||
# but others such as textured_model.zip should be available
|
# but others such as textured_model.zip should be available
|
||||||
res = client.get("/api/projects/{}/tasks/{}/".format(project.id, task.id))
|
res = client.get("/api/projects/{}/tasks/{}/".format(project.id, task.id))
|
||||||
|
|
|
@ -1 +1 @@
|
||||||
Subproject commit bfc90c9cec21a06b88ed92f202a0a79901b8962d
|
Subproject commit 3586a31f9071db5931b52c580bbf009f25e3b5ec
|
|
@ -180,14 +180,26 @@ If a [Task](#task) has been canceled or has failed processing, or has completed
|
||||||
|
|
||||||
### Orthophoto TMS layer
|
### Orthophoto TMS layer
|
||||||
|
|
||||||
`GET /api/projects/{project_id}/tasks/{task_id}/tiles.json`
|
`GET /api/projects/{project_id}/tasks/{task_id}/orthophoto/tiles.json`
|
||||||
|
|
||||||
`GET /api/projects/{project_id}/tasks/{task_id}/tiles/{Z}/{X}/{Y}.png`
|
`GET /api/projects/{project_id}/tasks/{task_id}/orthophoto/tiles/{Z}/{X}/{Y}.png`
|
||||||
|
|
||||||
After a task has been successfully processed, a TMS layer is made available for inclusion in programs such as [Leaflet](http://leafletjs.com/) or [Cesium](http://cesiumjs.org).
|
After a task has been successfully processed, a TMS layer is made available for inclusion in programs such as [Leaflet](http://leafletjs.com/) or [Cesium](http://cesiumjs.org).
|
||||||
|
|
||||||
<aside class="notice">If you use <a href="http://leafletjs.com/" target="_blank">Leaflet</a>, you'll need to pass the authentication token via querystring: /api/projects/{project_id}/tasks/{task_id}/tiles/{Z}/{X}/{Y}.png?jwt=your_token</aside>
|
<aside class="notice">If you use <a href="http://leafletjs.com/" target="_blank">Leaflet</a>, you'll need to pass the authentication token via querystring: /api/projects/{project_id}/tasks/{task_id}/tiles/{Z}/{X}/{Y}.png?jwt=your_token</aside>
|
||||||
|
|
||||||
|
### Surface Model TMS layer
|
||||||
|
|
||||||
|
`GET /api/projects/{project_id}/tasks/{task_id}/dsm/tiles.json`
|
||||||
|
|
||||||
|
`GET /api/projects/{project_id}/tasks/{task_id}/dsm/tiles/{Z}/{X}/{Y}.png`
|
||||||
|
|
||||||
|
### Terrain Model TMS layer
|
||||||
|
|
||||||
|
`GET /api/projects/{project_id}/tasks/{task_id}/dtm/tiles.json`
|
||||||
|
|
||||||
|
`GET /api/projects/{project_id}/tasks/{task_id}/dtm/tiles/{Z}/{X}/{Y}.png`
|
||||||
|
|
||||||
### Pending Actions
|
### Pending Actions
|
||||||
|
|
||||||
In some circumstances, a [Task](#task) can have a pending action that requires some amount of time to be performed.
|
In some circumstances, a [Task](#task) can have a pending action that requires some amount of time to be performed.
|
||||||
|
|
Ładowanie…
Reference in New Issue