diff --git a/app/assets/scripts/actions/actions.js b/app/assets/scripts/actions/actions.js
index 06bf6b27..d752b673 100644
--- a/app/assets/scripts/actions/actions.js
+++ b/app/assets/scripts/actions/actions.js
@@ -11,6 +11,23 @@ module.exports = Reflux.createActions({
'resultsChange': {},
+ // Filter actios
+ 'setDateFilter': {
+ shouldEmit: function (val) {
+ return [ 'all', 'week', 'month', 'year' ].indexOf(val) >= 0;
+ }
+ },
+ 'setResolutionFilter': {
+ shouldEmit: function (val) {
+ return [ 'all', 'low', 'medium', 'high' ].indexOf(val) >= 0;
+ }
+ },
+ 'setDataTypeFilter': {
+ shouldEmit: function (val) {
+ return [ 'all', 'service' ].indexOf(val) >= 0;
+ }
+ },
+
// Results pane related actions.
'resultItemSelect': {},
'resultItemView': {},
@@ -29,4 +46,7 @@ module.exports = Reflux.createActions({
'goToLatest': {},
'geocoderResult': {},
-});
\ No newline at end of file
+
+
+ 'miniMapClick': {},
+});
diff --git a/app/assets/scripts/components/app.js b/app/assets/scripts/components/app.js
index d3a981fd..4907424c 100644
--- a/app/assets/scripts/components/app.js
+++ b/app/assets/scripts/components/app.js
@@ -4,13 +4,31 @@ var Router = require('react-router');
var RouteHandler = Router.RouteHandler;
var InfoModal = require('./modals/info_modal');
var WelcomeModal = require('./modals/welcome_modal');
+var MessageModal = require('./modals/message_modal');
var Header = require('./header');
+var actions = require('../actions/actions');
var App = React.createClass({
+ mixins: [ Router.State ],
- aboutClickHandler: function(e) {
- e.preventDefault();
- actions.openModal('about');
+ componentDidMount: function () {
+ // Pull the search filter state from the URL. Why is this here instead
+ // of in the Filters component? Because we want to ensure that we set
+ // these filter parameters BEFORE the map component loads, since that is
+ // where the map move action will get fired, triggering the first API load.
+ //
+ // TODO: this is really a stopgap until we integrate the router more
+ // fully. (See routes.js for more.)
+ var params = this.getQuery();
+ if (params.date) {
+ actions.setDateFilter(params.date);
+ }
+ if (params.resolution) {
+ actions.setResolutionFilter(params.resolution);
+ }
+ if (params.type) {
+ actions.setDataTypeFilter(params.type);
+ }
},
render: function() {
@@ -22,6 +40,7 @@ var App = React.createClass({
+
);
}
diff --git a/app/assets/scripts/components/filters.js b/app/assets/scripts/components/filters.js
new file mode 100644
index 00000000..0644e41b
--- /dev/null
+++ b/app/assets/scripts/components/filters.js
@@ -0,0 +1,97 @@
+'use strict';
+var React = require('react/addons');
+var Reflux = require('reflux');
+var Router = require('react-router');
+var Dropdown = require('./shared/dropdown');
+var actions = require('../actions/actions');
+var searchQuery = require('../stores/search_query_store');
+
+var Filters = module.exports = React.createClass({
+ mixins: [
+ Reflux.listenTo(searchQuery, 'onSearchQuery'),
+ Router.Navigation,
+ Router.State
+ ],
+
+ getInitialState: function () {
+ return {
+ date: 'all',
+ resolution: 'all',
+ dataType: 'all'
+ }
+ },
+
+ onSearchQuery: function (data) {
+ this.setState(data);
+ },
+
+ setDate: function (d) {
+ actions.setDateFilter(d.key);
+ this._updateUrl('date', d.key);
+ },
+
+ setResolution: function (d) {
+ actions.setResolutionFilter(d.key);
+ this._updateUrl('resolution', d.key)
+ },
+
+ setDataType: function (d) {
+ actions.setDataTypeFilter(d.key);
+ this._updateUrl('type', d.key);
+ },
+
+ _updateUrl: function (prop, value) {
+ var query = this.getQuery();
+ if (value === 'all') {
+ delete query[prop]
+ } else {
+ query[prop] = value;
+ }
+ var routes = this.getRoutes();
+ this.replaceWith(routes[routes.length - 1].name, this.getParams(), query);
+ },
+
+
+ render: function() {
+ function filterItem (property, clickHandler, d) {
+ var klass = this.state[property] === d.key ? 'active' : '';
+ var click = clickHandler.bind(this, d);
+ return (
+
diff --git a/app/assets/scripts/components/home.js b/app/assets/scripts/components/home.js
index d5783351..0d281c21 100644
--- a/app/assets/scripts/components/home.js
+++ b/app/assets/scripts/components/home.js
@@ -1,6 +1,7 @@
'use strict';
var React = require('react/addons');
var MapBoxMap = require('./map');
+var MiniMap = require('./minimap');
var ResultsPane = require('./results_pane');
var Home = React.createClass({
@@ -8,6 +9,7 @@ var Home = React.createClass({
return (
+
);
diff --git a/app/assets/scripts/components/map.js b/app/assets/scripts/components/map.js
index 1bfb987a..39aac0fe 100644
--- a/app/assets/scripts/components/map.js
+++ b/app/assets/scripts/components/map.js
@@ -2,16 +2,20 @@
require('mapbox.js');
var React = require('react/addons');
var Reflux = require('reflux');
+var Router = require('react-router');
+var _ = require('lodash');
var overlaps = require('turf-overlaps');
// Not working. Using cdn. (turf.intersect was throwing a weird error)
//var turf = require('turf');
var actions = require('../actions/actions');
var mapStore = require('../stores/map_store');
var resultsStore = require('../stores/results_store');
+var searchQueryStore = require('../stores/search_query_store');
var utils = require('../utils/utils');
var dsZoom = require('../utils/ds_zoom');
+var config = require('../config.js');
-L.mapbox.accessToken = 'pk.eyJ1IjoiZGV2c2VlZCIsImEiOiJnUi1mbkVvIn0.018aLhX0Mb0tdtaT2QNe2Q';
+L.mapbox.accessToken = 'pk.eyJ1IjoiaG90IiwiYSI6IjU3MjE1YTYxZGM2YmUwMDIxOTg2OGZmNWU0NzRlYTQ0In0.MhK7SIwO00rhs3yMudBfIw';
var Map = React.createClass({
// Connect to the store "mapStore". Whenever the store calls "this.trigger()"
@@ -20,6 +24,7 @@ var Map = React.createClass({
// removing the listener when the component is unmounted.
mixins: [
Reflux.listenTo(mapStore, "onMapData"),
+ Reflux.listenTo(searchQueryStore, "onSearchQueryChanged"),
Reflux.listenTo(actions.mapSquareSelected, "onMapSquareSelected"),
Reflux.listenTo(actions.mapSquareUnselected, "onMapSquareUnselected"),
Reflux.listenTo(actions.resultOver, "onResultOver"),
@@ -28,8 +33,13 @@ var Map = React.createClass({
Reflux.listenTo(actions.resultItemView, "onResultItemView"),
Reflux.listenTo(actions.resultListView, "onResultListView"),
+ Reflux.listenTo(actions.miniMapClick, "onMiniMapClick"),
+
Reflux.listenTo(actions.goToLatest, "onGoToLatest"),
Reflux.listenTo(actions.geocoderResult, "onGeocoderResult"),
+
+ Router.Navigation,
+ Router.State
],
map: null,
@@ -50,6 +60,10 @@ var Map = React.createClass({
// square that contains it. Check updateGrid()
selectIntersecting: null,
+ // If there's a selected square in the path, we store it and then select the
+ // correct one when the grid is updating.
+ routerSelectedSquare: null,
+
getInitialState: function() {
return {
loading: true
@@ -62,6 +76,26 @@ var Map = React.createClass({
mapData: data,
loading: false
});
+
+ var sqrFeature = mapStore.getSelectedSquare();
+ if (sqrFeature !== null) {
+ var intersected = mapStore.getResultsIntersect(sqrFeature);
+ if (intersected.length > 0) {
+ actions.resultsChange(intersected);
+ } else {
+ actions.mapSquareUnselected();
+ }
+ }
+ },
+
+ onSearchQueryChanged: function() {
+ this.setState({ loading: true });
+ },
+
+ // Actions listener.
+ onMiniMapClick: function(latlng) {
+ // Remove footprint highlight.
+ this.map.setView(latlng);
},
// Actions listener.
@@ -98,7 +132,18 @@ var Map = React.createClass({
// Coordinates must be inverted for panTo.
this.map.panTo([sqrFeature.properties.centroid[1], sqrFeature.properties.centroid[0]]);
- this.updateGrid();
+ // Set the correct path.
+ var params = this.getParams();
+ var route = params.item_id ? 'item' : 'results';
+
+ var selectedSquare = mapStore.getSelectedSquareCenter();
+
+ params.map = this.mapViewToString();
+ params.square = selectedSquare[1] + ',' + selectedSquare[0];
+
+ this.replaceWith(route, params, this.getQuery());
+
+ //this.updateGrid();
},
// Actions listener.
@@ -107,6 +152,10 @@ var Map = React.createClass({
this.map.removeLayer(this.overImageLayer);
}
+ // Set the correct path.
+ var mapLocation = this.mapViewToString();
+ this.replaceWith('map', { map: mapLocation }, this.getQuery());
+
actions.resultsChange([]);
this.updateGrid();
},
@@ -135,7 +184,7 @@ var Map = React.createClass({
latest.properties.centroid = latestCenter;
this.selectIntersecting = latest;
// Move the map
- this.map.setView([latestCenter.geometry.coordinates[1], latestCenter.geometry.coordinates[0]], 8);
+ this.map.setView([latestCenter.geometry.coordinates[1], latestCenter.geometry.coordinates[0]], config.map.initialZoom);
}
},
@@ -148,12 +197,13 @@ var Map = React.createClass({
},
// Redraws the line grid.
- // This is a pixel grid with 200px squares at zoom level 8.
+ // This is a pixel grid with config.map.grid.pxSize squares
+ // at zoom level config.map.grid.atZoom.
updateFauxGrid: function() {
var bounds = this.map.getBounds();
var extent = [bounds.getWest(), bounds.getSouth(), bounds.getEast(), bounds.getNorth()];
- var grid = this.linePixelGrid(extent, 200, 8);
+ var grid = this.linePixelGrid(extent, config.map.grid.pxSize, config.map.grid.atZoom);
this.fauxLineGridLayer.clearLayers().addData(grid);
this.fauxLineGridLayer.eachLayer(function(l) {
@@ -184,20 +234,21 @@ var Map = React.createClass({
},
// Updates the colored grid.
- // This is a pixel grid with 200px squares at zoom level 8.
+ // This is a pixel grid with config.map.grid.pxSize squares
+ // at zoom level config.map.grid.atZoom.
// It is separated from the line grid to allow independent styling of
// the stroke/content.
updateGrid: function() {
var _this = this;
this.gridLayer.clearLayers();
- // Do not draw below zoom level 6
- if (this.map.getZoom() < 6) { return; }
+ // Do not draw below zoom level config.map.interactiveGridZoomLimit
+ if (this.map.getZoom() < config.map.interactiveGridZoomLimit) { return; }
var bounds = this.map.getBounds();
var extent = [bounds.getWest(), bounds.getSouth(), bounds.getEast(), bounds.getNorth()];
// Square grid to color.
- var squareGrid = this.squarePixelGrid(extent, 200, 8);
+ var squareGrid = this.squarePixelGrid(extent, config.map.grid.pxSize, config.map.grid.atZoom);
/*
// How this works:
@@ -279,11 +330,23 @@ var Map = React.createClass({
this.gridLayer.addData(squareGrid);
this.gridLayer.eachLayer(function(l) {
L.DomUtil.addClass(l._path, 'gs');
- var featureCenter = null;
+ var featureCenter = turf.centroid(l.feature);
+ l.feature.properties.featureCenter = featureCenter;
+
+ // If there is a selected square in the path, select the correct one.
+ if (_this.routerSelectedSquare) {
+ if (_this.routerSelectedSquare[0] == featureCenter.geometry.coordinates[1] &&
+ _this.routerSelectedSquare[1] == featureCenter.geometry.coordinates[0]) {
+ // Done with selecting.
+ _this.routerSelectedSquare = null;
+ // Trigger action.
+ actions.mapSquareSelected(l.feature);
+ return;
+ }
+ }
// Select the square that intersects the stored image.
if (_this.selectIntersecting) {
- featureCenter = turf.centroid(l.feature);
var latestFeature = utils.getPolygonFeature(_this.selectIntersecting.coordinates);
if (turf.inside(featureCenter, latestFeature) || turf.inside(_this.selectIntersecting.properties.centroid, l.feature) || overlaps(latestFeature, l.feature)) {
@@ -325,6 +388,15 @@ var Map = React.createClass({
L.DomUtil.addClass(l._path, 'gs-density-low');
}
+ var p = L.popup({
+ autoPan: false,
+ closeButton: false,
+ offset: L.point(0, 10),
+ className: 'gs-tooltip-count'
+ }).setContent(intersectCount.toString());
+
+ l.bindPopup(p);
+
});
this.gridLayer.bringToBack();
return this;
@@ -335,14 +407,30 @@ var Map = React.createClass({
componentDidMount: function() {
console.log('componentDidMount MapBoxMap');
var _this = this;
- var view = [60.177, 25.148];
+ var view = config.map.initialView;
+ var zoom = config.map.initialZoom;
+
+ // Map position from path.
+ var routerMap = this.getParams().map;
+ if (routerMap) {
+ routerMap = this.stringToMapView(routerMap);
+ view = [routerMap.lat, routerMap.lng];
+ zoom = routerMap.zoom;
+ }
+
+ // Check if there's a selected square in the path.
+ var routerSquare = this.getParams().square;
+ if (routerSquare) {
+ this.routerSelectedSquare = routerSquare.split(',');
+ view = [this.routerSelectedSquare[0], this.routerSelectedSquare[1]];
+ }
- this.map = L.mapbox.map(this.getDOMNode().querySelector('#map'), 'devseed.m9i692do', {
+ this.map = L.mapbox.map(this.getDOMNode().querySelector('#map'), config.map.baseLayer, {
zoomControl: false,
- minZoom : 4,
- //maxZoom : 18,
+ minZoom : config.map.minZoom,
+ maxZoom : config.map.maxZoom,
maxBounds: L.latLngBounds([-90, -180], [90, 180])
- }).setView(view, 6);
+ }).setView(view, zoom);
// Custom zoom control.
var zoom = new dsZoom({
@@ -360,6 +448,9 @@ var Map = React.createClass({
this.gridLayer = L.geoJson(null, { style: L.mapbox.simplestyle.style }).addTo(this.map);
// On click select the square.
this.gridLayer.on('click', function(e) {
+ // Ensure that the popup doesn't open.
+ e.layer.closePopup();
+
// No previous square selected.
if (mapStore.isSelectedSquare()) {
// Unselect.
@@ -377,22 +468,44 @@ var Map = React.createClass({
this.gridLayer.on('mouseover', function(e) {
if (!mapStore.isSelectedSquare() && e.layer.feature.properties.intersectCount > 0) {
L.DomUtil.addClass(e.layer._path, 'gs-highlight');
+ // Open popup on square center.
+ var sqrCenter = e.layer.feature.properties.featureCenter.geometry.coordinates;
+ e.layer.openPopup([sqrCenter[1], sqrCenter[0]]);
}
});
// On mouseout remove gs-highlight.
this.gridLayer.on('mouseout', function(e) {
L.DomUtil.removeClass(e.layer._path, 'gs-highlight');
+ e.layer.closePopup();
});
// Footprint layer.
this.overFootprintLayer = L.geoJson(null, { style: L.mapbox.simplestyle.style }).addTo(this.map);
+
// Map move listener.
- this.map.on('moveend', function() {
- _this.setState({loading: true});
+ var onMoveEnd = _.debounce(function() {
+ // Compute new map location for the path .
+ var mapLocation = _this.mapViewToString();
+ // Preserve other params if any.
+ var params = _this.getParams();
+ params.map = mapLocation;
+
+ // Check what's the route to use.
+ var route = 'map';
+ if (params.item_id) {
+ route = 'item';
+ }
+ else if (params.square) {
+ route = 'results';
+ }
+ _this.replaceWith(route, params, _this.getQuery());
+
actions.mapMove(_this.map);
_this.updateFauxGrid();
- });
+ }, 300)
+ this.map.on('moveend', onMoveEnd);
+ this.map.on('movestart', onMoveEnd.cancel);
// Create fauxGrid.
this.updateFauxGrid();
@@ -417,6 +530,34 @@ var Map = React.createClass({
);
},
+ /**
+ * Converts the map view (coords + zoom) to use on the path.
+ *
+ * @return string
+ */
+ mapViewToString: function() {
+ var center = this.map.getCenter();
+ var zoom = this.map.getZoom();
+ return center.lat + ',' + center.lng + ',' + zoom;
+ },
+
+ /**
+ * Converts a path string like 60.359564131824214,4.010009765624999,6
+ * to a readable object
+ *
+ * @param String
+ * string to convert
+ * @return object
+ */
+ stringToMapView: function(string) {
+ var data = string.split(',');
+ return {
+ lat: data[0],
+ lng: data[1],
+ zoom: data[2],
+ }
+ },
+
/**
* Creates a equidistant pixel line grid for the given bbox.
* The grid drawing will start form the closest cellSize multiple thus
@@ -538,4 +679,4 @@ var Map = React.createClass({
});
-module.exports = Map;
\ No newline at end of file
+module.exports = Map;
diff --git a/app/assets/scripts/components/minimap.js b/app/assets/scripts/components/minimap.js
new file mode 100644
index 00000000..0b8baf09
--- /dev/null
+++ b/app/assets/scripts/components/minimap.js
@@ -0,0 +1,105 @@
+'use strict';
+require('mapbox.js');
+var React = require('react/addons');
+var Reflux = require('reflux');
+// Not working. Using cdn. (turf.intersect was throwing a weird error)
+//var turf = require('turf');
+var actions = require('../actions/actions');
+var mapStore = require('../stores/map_store');
+var config = require('../config.js');
+
+var MiniMap = React.createClass({
+ mixins: [
+ Reflux.listenTo(actions.mapMove, "onMapMove"),
+ Reflux.listenTo(actions.mapSquareSelected, "onMapSquareSelected"),
+ Reflux.listenTo(actions.mapSquareUnselected, "onMapSquareUnselected")
+ ],
+
+ map: null,
+
+ viewfinder: null,
+ targetLines: null,
+
+ // Actions listener.
+ onMapMove: function(mainmap) {
+ var b = mainmap.getBounds();
+
+ this.viewfinder.setLatLngs([
+ b.getNorthEast(),
+ b.getNorthWest(),
+ b.getSouthWest(),
+ b.getSouthEast()
+ ]).addTo(this.map);
+ },
+
+ onMapSquareSelected: function(sqrFeature) {
+ var center = sqrFeature.properties.centroid;
+
+ this.targetLines.setLatLngs([
+ [
+ [-90, center[0]],
+ [90, center[0]]
+ ],
+ [
+ [center[1], -220],
+ [center[1], 220]
+ ]
+ ]);
+ },
+
+ onMapSquareUnselected: function() {
+ this.targetLines.clearLayers();
+ },
+
+ // Lifecycle method.
+ // Called once as soon as the component has a DOM representation.
+ componentDidMount: function() {
+ console.log('componentDidMount MiniMap');
+ var _this = this;
+
+ this.map = L.mapbox.map(this.getDOMNode(), config.map.baseLayer, {
+ center: [0, 0],
+
+ zoomControl: false,
+ attributionControl: false,
+ dragging: false,
+ touchZoom: false,
+ scrollWheelZoom: false,
+ doubleClickZoom: false,
+ boxZoom: false,
+
+ maxBounds: L.latLngBounds([-90, -180], [90, 180])
+ }).fitBounds(L.latLngBounds([-90, -180], [90, 180]));
+
+ this.viewfinder = L.polygon([], {
+ clickable: false,
+ color: '#1f3b45',
+ weight: 0.5
+ }).addTo(this.map);
+
+
+ this.targetLines = L.multiPolyline([], {
+ clickable: false,
+ color: '#1f3b45',
+ weight: 0.5
+ }).addTo(this.map);
+
+ console.log(this.targetLines);
+ this.map.on('click', function(e) {
+ actions.miniMapClick(e.latlng);
+ });
+ },
+
+ // Lifecycle method.
+ // Called when the component gets updated.
+ componentDidUpdate: function(/*prevProps, prevState*/) {
+ console.log('componentDidUpdate');
+ },
+
+ render: function() {
+ return (
);
+ },
+
+});
+
+module.exports = MiniMap;
\ No newline at end of file
diff --git a/app/assets/scripts/components/modals/base_modal.js b/app/assets/scripts/components/modals/base_modal.js
index 552ffb50..8aac837c 100644
--- a/app/assets/scripts/components/modals/base_modal.js
+++ b/app/assets/scripts/components/modals/base_modal.js
@@ -3,6 +3,7 @@
var React = require('react/addons');
var ReactCSSTransitionGroup = React.addons.CSSTransitionGroup;
var Reflux = require('reflux');
+var Keys = require('react-keybinding');
var actions = require('../../actions/actions');
/**
@@ -21,8 +22,15 @@ var actions = require('../../actions/actions');
var BModal = React.createClass({
mixins: [
Reflux.listenTo(actions.openModal, 'onOpenModal'),
+ Keys
],
+ keybindings: {
+ 'esc': function() {
+ this.setState({ revealed: false });
+ }
+ },
+
onOpenModal: function(which) {
if (which == this.props.type) {
this.setState({ revealed: true });
diff --git a/app/assets/scripts/components/modals/message_modal.js b/app/assets/scripts/components/modals/message_modal.js
new file mode 100644
index 00000000..3e1e76d2
--- /dev/null
+++ b/app/assets/scripts/components/modals/message_modal.js
@@ -0,0 +1,52 @@
+var React = require('react/addons');
+var Reflux = require('reflux');
+var BModal = require('./base_modal');
+var actions = require('../../actions/actions');
+
+var MessageModal = React.createClass({
+ mixins: [
+ Reflux.listenTo(actions.openModal, 'onOpenModal')
+ ],
+
+ getInitialState: function() {
+ return {
+ title: 'Message',
+ message: ''
+ }
+ },
+
+ onOpenModal: function (which, data) {
+ if (which === 'message' && data) {
+ this.setState(data);
+ }
+ },
+
+ getHeader: function () {
+ return (
{this.state.title}
);
+ },
+
+ getBody: function() {
+ return (
+
+ {this.state.message}
+
+ );
+ },
+
+ getFooter: function () {
+ return false;
+ },
+
+ render: function () {
+ return (
+
+ );
+ }
+});
+
+module.exports = MessageModal;
diff --git a/app/assets/scripts/components/results_item.js b/app/assets/scripts/components/results_item.js
index 3a0b24b6..7c450231 100644
--- a/app/assets/scripts/components/results_item.js
+++ b/app/assets/scripts/components/results_item.js
@@ -1,11 +1,36 @@
'use strict';
+var qs = require('querystring');
+var $ = require('jquery');
var React = require('react/addons');
+var Keys = require('react-keybinding');
+var Router = require('react-router');
var actions = require('../actions/actions');
-var ZcInput = require('./shared/zc_input');
+var ZcButton = require('./shared/zc_button');
+var Dropdown = require('./shared/dropdown');
var utils = require('../utils/utils');
+var actions = require('../actions/actions');
+var prettyBytes = require('pretty-bytes');
var ResultsItem = React.createClass({
+ mixins: [
+ Keys,
+ Router.State
+ ],
+
+ keybindings: {
+ 'arrow-left': function() {
+ if (this.props.pagination.current > 1) {
+ actions.prevResult();
+ }
+ },
+ 'arrow-right': function() {
+ if (this.props.pagination.current < this.props.pagination.total) {
+ actions.nextResult();
+ }
+ }
+ },
+
prevResult: function(e) {
e.preventDefault();
actions.prevResult();
@@ -19,6 +44,47 @@ var ResultsItem = React.createClass({
actions.nextResult();
},
+ onCopy: function(e) {
+ return this.getDOMNode().querySelector('[data-hook="copy:data"]').value;
+ },
+
+ onOpenJosm: function(d) {
+ var self = this;
+ var source = 'OpenAerialMap - ' + d.provider + ' - ' + d.uuid;
+ // Reference:
+ // http://josm.openstreetmap.de/wiki/Help/Preferences/RemoteControl#load_and_zoom
+ $.get('http://127.0.0.1:8111/load_and_zoom?' + qs.stringify({
+ left: d.bbox[0],
+ right: d.bbox[2],
+ bottom: d.bbox[1],
+ top: d.bbox[3],
+ source: source
+ }))
+ .success(function (data) {
+ // Reference:
+ // http://josm.openstreetmap.de/wiki/Help/Preferences/RemoteControl#imagery
+ // Note: `url` needs to be the last parameter.
+ $.get('http://127.0.0.1:8111/imagery?' + qs.stringify({
+ type: 'tms',
+ title: source
+ }) + '&url=' + d.properties.tms)
+ .success(function () {
+ // all good!
+ actions.openModal('message', {
+ title: 'Success',
+ message: 'This scene has been loaded into JOSM.'
+ });
+ });
+ })
+ .fail(function (err) {
+ console.error(err);
+ actions.openModal('message', {
+ title: 'Error',
+ message:
Could not connect to JOSM via Remote Control. Is JOSM configured to allow remote control?
+ });
+ });
+ },
+
render: function() {
var d = this.props.data;
var pagination = this.props.pagination;
@@ -28,7 +94,32 @@ var ResultsItem = React.createClass({
var tmsOptions = null;
if (d.properties.tms) {
- tmsOptions = (
);
+ // Generate the iD URL:
+ // grab centroid of the footprint
+ var centroid = turf.centroid(d.geojson).geometry.coordinates;
+ // cheat by using current zoom level
+ var zoom = this.getParams().map.split(',')[2]
+ var idUrl = 'http://www.openstreetmap.org/edit' +
+ '#map=' + [zoom, centroid[1], centroid[0]].join('/') +
+ '?' + qs.stringify({
+ editor: 'id',
+ background: 'custom:' + d.properties.tms
+ });
+
+ tmsOptions = (
+
+ );
}
var blurImage = {
@@ -52,14 +143,20 @@ var ResultsItem = React.createClass({
Download