kopia lustrzana https://github.com/OpenDroneMap/WebODM
Added switch buttons for map type, fixed API endpoints, assets import fix
rodzic
186f4bac5e
commit
f7fa29cd3c
|
@ -18,7 +18,7 @@ urlpatterns = [
|
|||
url(r'^', include(router.urls)),
|
||||
url(r'^', include(tasks_router.urls)),
|
||||
|
||||
url(r'projects/(?P<project_pk>[^/.]+)/tasks/(?P<pk>[^/.]+)/tiles/(?P<tile_type>orthophoto|dsm|dtm)/(?P<z>[\d]+)/(?P<x>[\d]+)/(?P<y>[\d]+)\.png$', TaskTiles.as_view()),
|
||||
url(r'projects/(?P<project_pk>[^/.]+)/tasks/(?P<pk>[^/.]+)/(?P<tile_type>orthophoto|dsm|dtm)/tiles/(?P<z>[\d]+)/(?P<x>[\d]+)/(?P<y>[\d]+)\.png$', TaskTiles.as_view()),
|
||||
url(r'projects/(?P<project_pk>[^/.]+)/tasks/(?P<pk>[^/.]+)/(?P<tile_type>orthophoto|dsm|dtm)/tiles\.json$', TaskTilesJson.as_view()),
|
||||
|
||||
url(r'projects/(?P<project_pk>[^/.]+)/tasks/(?P<pk>[^/.]+)/download/(?P<asset>.+)$', TaskDownloads.as_view()),
|
||||
|
|
|
@ -72,8 +72,8 @@ class Project(models.Model):
|
|||
def tasks(self):
|
||||
return self.task_set.only('id')
|
||||
|
||||
def get_tiles_json_data(self):
|
||||
return [task.get_tiles_json_data() for task in self.task_set.filter(
|
||||
def get_map_items(self):
|
||||
return [task.get_map_items() for task in self.task_set.filter(
|
||||
status=status_codes.COMPLETED
|
||||
).filter(Q(orthophoto_extent__isnull=False) | Q(dsm_extent__isnull=False) | Q(dtm_extent__isnull=False))
|
||||
.only('id', 'project_id')]
|
||||
|
@ -446,11 +446,11 @@ class Task(models.Model):
|
|||
# Populate *_extent fields
|
||||
extent_fields = [
|
||||
(os.path.realpath(self.assets_path("odm_orthophoto", "odm_orthophoto.tif")),
|
||||
self.orthophoto_extent),
|
||||
(os.path.realpath(self.assets_path("odm_dsm", "dsm.tif")),
|
||||
self.dsm_extent),
|
||||
(os.path.realpath(self.assets_path("odm_dtm", "dtm.tif")),
|
||||
self.dtm_extent),
|
||||
'orthophoto_extent'),
|
||||
(os.path.realpath(self.assets_path("odm_dem", "dsm.tif")),
|
||||
'dsm_extent'),
|
||||
(os.path.realpath(self.assets_path("odm_dem", "dtm.tif")),
|
||||
'dtm_extent'),
|
||||
]
|
||||
|
||||
for raster_path, field in extent_fields:
|
||||
|
@ -460,7 +460,8 @@ class Task(models.Model):
|
|||
extent = OGRGeometry.from_bbox(raster.extent)
|
||||
|
||||
# It will be implicitly transformed into the SRID of the model’s field
|
||||
field = GEOSGeometry(extent.wkt, srid=raster.srid)
|
||||
# self.field = GEOSGeometry(...)
|
||||
setattr(self, field, GEOSGeometry(extent.wkt, srid=raster.srid))
|
||||
|
||||
logger.info("Populated extent field with {} for {}".format(raster_path, self))
|
||||
|
||||
|
@ -487,7 +488,7 @@ class Task(models.Model):
|
|||
def get_tile_json_url(self, tile_type):
|
||||
return "/api/projects/{}/tasks/{}/{}/tiles.json".format(self.project.id, self.id, tile_type)
|
||||
|
||||
def get_tiles_json_data(self):
|
||||
def get_map_items(self):
|
||||
types = []
|
||||
if 'orthophoto.tif' in self.available_assets: types.append('orthophoto')
|
||||
if 'dsm.tif' in self.available_assets: types.append('dsm')
|
||||
|
|
|
@ -5,13 +5,13 @@ import $ from 'jquery';
|
|||
|
||||
class MapView extends React.Component {
|
||||
static defaultProps = {
|
||||
tiles: [],
|
||||
mapItems: [],
|
||||
selectedMapType: 'orthophoto',
|
||||
title: ""
|
||||
};
|
||||
|
||||
static propTypes = {
|
||||
tiles: React.PropTypes.array.isRequired, // list of dictionaries where each dict is a {mapType: 'orthophoto', url: <tiles.json>},
|
||||
mapItems: React.PropTypes.array.isRequired, // list of dictionaries where each dict is a {mapType: 'orthophoto', url: <tiles.json>},
|
||||
selectedMapType: React.PropTypes.oneOf(['orthophoto', 'dsm', 'dtm']),
|
||||
title: React.PropTypes.string,
|
||||
};
|
||||
|
@ -21,12 +21,39 @@ class MapView extends React.Component {
|
|||
|
||||
this.state = {
|
||||
opacity: 100,
|
||||
mapType: props.mapType
|
||||
selectedMapType: props.selectedMapType,
|
||||
tiles: this.getTilesByMapType(props.selectedMapType)
|
||||
};
|
||||
|
||||
console.log(props);
|
||||
|
||||
this.updateOpacity = this.updateOpacity.bind(this);
|
||||
this.getTilesByMapType = this.getTilesByMapType.bind(this);
|
||||
this.handleMapTypeButton = this.handleMapTypeButton.bind(this);
|
||||
}
|
||||
|
||||
getTilesByMapType(type){
|
||||
// Go through the list of tiles and return
|
||||
// only those that match a particular type
|
||||
const tiles = [];
|
||||
|
||||
this.props.mapItems.forEach(mapItem => {
|
||||
mapItem.tiles.forEach(tile => {
|
||||
if (tile.type === type) tiles.push({
|
||||
url: tile.url,
|
||||
meta: mapItem.meta
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
return tiles;
|
||||
}
|
||||
|
||||
handleMapTypeButton(type){
|
||||
return () => {
|
||||
this.setState({
|
||||
selectedMapType: type,
|
||||
tiles: this.getTilesByMapType(type)
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
updateOpacity(evt) {
|
||||
|
@ -40,29 +67,37 @@ class MapView extends React.Component {
|
|||
const mapTypeButtons = [
|
||||
{
|
||||
label: "Orthophoto",
|
||||
key: "orthophoto"
|
||||
type: "orthophoto"
|
||||
},
|
||||
{
|
||||
label: "Surface Model",
|
||||
key: "dsm"
|
||||
type: "dsm"
|
||||
},
|
||||
{
|
||||
label: "Terrain Model",
|
||||
key: "dtm"
|
||||
type: "dtm"
|
||||
}
|
||||
];
|
||||
|
||||
return (<div className="map-view">
|
||||
<div className="map-type-selector btn-group" role="group">
|
||||
<button className="btn btn-sm btn-primary active">Preview</button>
|
||||
<button className="btn btn-sm btn-secondary">Source Code</button>
|
||||
{mapTypeButtons.map(mapType =>
|
||||
<button
|
||||
key={mapType.type}
|
||||
onClick={this.handleMapTypeButton(mapType.type)}
|
||||
className={"btn btn-sm " + (mapType.type === this.state.selectedMapType ? "btn-default" : "btn-secondary")}>{mapType.label}</button>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{this.props.title ?
|
||||
<h3><i className="fa fa-globe"></i> {this.props.title}</h3>
|
||||
: ""}
|
||||
|
||||
<Map tiles={this.props.tiles} showBackground={true} opacity={opacity}/>
|
||||
<Map
|
||||
tiles={this.state.tiles}
|
||||
showBackground={true}
|
||||
opacity={opacity}
|
||||
mapType={this.state.selectedMapType} />
|
||||
<div className="opacity-slider">
|
||||
Opacity: <input type="range" step="1" value={opacity} onChange={this.updateOpacity} />
|
||||
</div>
|
||||
|
|
|
@ -45,11 +45,96 @@ class Map extends React.Component {
|
|||
this.imageryLayers = [];
|
||||
this.basemaps = {};
|
||||
this.mapBounds = null;
|
||||
|
||||
this.loadImageryLayers = this.loadImageryLayers.bind(this);
|
||||
}
|
||||
|
||||
loadImageryLayers(){
|
||||
const { tiles } = this.props,
|
||||
assets = AssetDownloads.excludeSeparators();
|
||||
|
||||
// Remove all previous imagery layers
|
||||
this.imageryLayers.forEach(layer => layer.remove());
|
||||
this.imageryLayers = [];
|
||||
|
||||
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
|
||||
}).addTo(this.map);
|
||||
|
||||
// Associate metadata with this layer
|
||||
meta.name = info.name;
|
||||
layer[Symbol.for("meta")] = meta;
|
||||
|
||||
// Show 3D switch button only if we have a single orthophoto
|
||||
const task = {
|
||||
id: meta.task,
|
||||
project: meta.project
|
||||
};
|
||||
|
||||
if (tiles.length === 1){
|
||||
this.setState({switchButtonTask: 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();
|
||||
};
|
||||
|
||||
layer.bindPopup(`<div class="title">${info.name}</div>
|
||||
<div>Bounds: [${layer.options.bounds.toBBoxString().split(",").join(", ")}]</div>
|
||||
<ul class="asset-links">
|
||||
${assets.map(asset => {
|
||||
return `<li><a href="${asset.downloadUrl(meta.project, meta.task)}">${asset.label}</a></li>`;
|
||||
}).join("")}
|
||||
</ul>
|
||||
|
||||
<button
|
||||
onclick="location.href='/3d/project/${task.project}/task/${task.id}/';"
|
||||
type="button"
|
||||
class="switchModeButton btn btn-sm btn-default btn-white">
|
||||
<i class="fa fa-cube"></i> 3D
|
||||
</button>
|
||||
`);
|
||||
|
||||
this.imageryLayers.push(layer);
|
||||
|
||||
let mapBounds = this.mapBounds || Leaflet.latLngBounds();
|
||||
mapBounds.extend(bounds);
|
||||
this.mapBounds = mapBounds;
|
||||
|
||||
done();
|
||||
})
|
||||
.fail((_, __, err) => done(err))
|
||||
);
|
||||
}, err => {
|
||||
if (err){
|
||||
this.setState({error: err.message || JSON.stringify(err)});
|
||||
reject(err);
|
||||
}else{
|
||||
resolve();
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
const { showBackground, tiles } = this.props;
|
||||
const assets = AssetDownloads.excludeSeparators();
|
||||
const { showBackground } = this.props;
|
||||
|
||||
this.map = Leaflet.map(this.container, {
|
||||
scrollWheelZoom: true,
|
||||
|
@ -88,73 +173,7 @@ class Map extends React.Component {
|
|||
}).addTo(this.map);
|
||||
this.map.attributionControl.setPrefix("");
|
||||
|
||||
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'
|
||||
}).addTo(this.map);
|
||||
|
||||
// Associate metadata with this layer
|
||||
meta.name = info.name;
|
||||
layer[Symbol.for("meta")] = meta;
|
||||
|
||||
// Show 3D switch button only if we have a single orthophoto
|
||||
const task = {
|
||||
id: meta.task,
|
||||
project: meta.project
|
||||
};
|
||||
|
||||
if (tiles.length === 1){
|
||||
this.setState({switchButtonTask: 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();
|
||||
};
|
||||
|
||||
layer.bindPopup(`<div class="title">${info.name}</div>
|
||||
<div>Bounds: [${layer.options.bounds.toBBoxString().split(",").join(", ")}]</div>
|
||||
<ul class="asset-links">
|
||||
${assets.map(asset => {
|
||||
return `<li><a href="${asset.downloadUrl(meta.project, meta.task)}">${asset.label}</a></li>`;
|
||||
}).join("")}
|
||||
</ul>
|
||||
|
||||
<button
|
||||
onclick="location.href='/3d/project/${task.project}/task/${task.id}/';"
|
||||
type="button"
|
||||
class="switchModeButton btn btn-sm btn-default btn-white">
|
||||
<i class="fa fa-cube"></i> 3D
|
||||
</button>
|
||||
`);
|
||||
|
||||
|
||||
this.imageryLayers.push(layer);
|
||||
|
||||
let mapBounds = this.mapBounds || Leaflet.latLngBounds();
|
||||
mapBounds.extend(bounds);
|
||||
this.mapBounds = mapBounds;
|
||||
|
||||
done();
|
||||
})
|
||||
.fail((_, __, err) => done(err))
|
||||
);
|
||||
}, err => {
|
||||
if (err) this.setState({error: err.message || JSON.stringify(err)});
|
||||
else{
|
||||
this.loadImageryLayers().then(() => {
|
||||
this.map.fitBounds(this.mapBounds);
|
||||
|
||||
// Add basemaps / layers control
|
||||
|
@ -179,14 +198,18 @@ class Map extends React.Component {
|
|||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
componentDidUpdate() {
|
||||
componentDidUpdate(prevProps) {
|
||||
this.imageryLayers.forEach(imageryLayer => {
|
||||
imageryLayer.setOpacity(this.props.opacity / 100);
|
||||
});
|
||||
|
||||
if (prevProps.tiles !== this.props.tiles){
|
||||
this.loadImageryLayers();
|
||||
}
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
|
|
|
@ -51,17 +51,15 @@ def map(request, project_pk=None, task_pk=None):
|
|||
if task_pk is not None:
|
||||
task = get_object_or_404(Task.objects.defer('orthophoto_extent', 'dsm_extent', 'dtm_extent'), pk=task_pk, project=project)
|
||||
title = task.name
|
||||
tiles = [task.get_tiles_json_data()]
|
||||
mapItems = [task.get_map_items()]
|
||||
else:
|
||||
title = project.name
|
||||
tiles = project.get_tiles_json_data()
|
||||
|
||||
print(tiles)
|
||||
mapItems = project.get_map_items()
|
||||
|
||||
return render(request, 'app/map.html', {
|
||||
'title': title,
|
||||
'params': {
|
||||
'tiles': json.dumps(tiles),
|
||||
'map-items': json.dumps(mapItems),
|
||||
'title': title
|
||||
}.items()
|
||||
})
|
||||
|
|
Ładowanie…
Reference in New Issue