Added switch buttons for map type, fixed API endpoints, assets import fix

pull/237/head
Piero Toffanin 2017-07-12 16:51:25 -04:00
rodzic 186f4bac5e
commit f7fa29cd3c
5 zmienionych plików z 154 dodań i 97 usunięć

Wyświetl plik

@ -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()),

Wyświetl plik

@ -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 models 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')

Wyświetl plik

@ -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>

Wyświetl plik

@ -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() {

Wyświetl plik

@ -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()
})