diff --git a/app/assets/javascripts/helpers/fallbackDataHelper.js b/app/assets/javascripts/helpers/fallbackDataHelper.js
index 93b9279d56..ef2746d09d 100644
--- a/app/assets/javascripts/helpers/fallbackDataHelper.js
+++ b/app/assets/javascripts/helpers/fallbackDataHelper.js
@@ -283,4 +283,4 @@ define([], function() {
};
return fallbackDataHelper;
-});
+});
\ No newline at end of file
diff --git a/app/assets/javascripts/map.js b/app/assets/javascripts/map.js
index ee31f81ba2..c20063c758 100644
--- a/app/assets/javascripts/map.js
+++ b/app/assets/javascripts/map.js
@@ -1,3 +1,4 @@
+/* eslint-disable */
/**
* Application entry point.
*/
@@ -29,6 +30,7 @@ require([
'map/views/NavMobileView',
'map/views/GuideView',
'map/views/controls/GuideButtonView',
+ 'map/views/ReactMapMiddleView',
'views/HeaderView',
'views/FooterView',
'views/NotificationsView',
@@ -56,6 +58,7 @@ require([
NavMobileView,
GuideView,
GuideButtonView,
+ ReactMapMiddleView,
HeaderView,
FooterView,
NotificationsView,
@@ -65,6 +68,9 @@ require([
$el: $('body'),
init: function() {
+ window.App = {
+ Views: {}
+ };
var router = new Router(this);
this._cartodbHack();
this._handlebarsPlugins();
@@ -80,6 +86,7 @@ require([
if (!Backbone.History.started) {
Backbone.history.start({ pushState: true });
}
+ window.dispatchEvent(new Event('mapLoaded'));
},
_fetchData: function() {
@@ -87,16 +94,20 @@ require([
// we shouldn't create any view.
countryService
.getCountries()
- .then(function(results) {
- this.countries = results;
- this._initViews();
- }.bind(this))
- .catch(function(e) {
- console.warn(e);
- // Fallback when request is timing out
- this.countries = FallbackDataHelper.getCountryNames();
- this._initViews();
- }.bind(this));
+ .then(
+ function(results) {
+ this.countries = results;
+ this._initViews();
+ }.bind(this)
+ )
+ .catch(
+ function(e) {
+ console.warn(e);
+ // Fallback when request is timing out
+ this.countries = FallbackDataHelper.getCountryNames();
+ this._initViews();
+ }.bind(this)
+ );
},
/**
* Initialize Application Views.
@@ -122,6 +133,7 @@ require([
new NotificationsView(this.map, this.countries);
new GuideView(this.map, this.countries);
new GuideButtonView(this.map, this.countries);
+ window.App.Views.ReactMapMiddleView = new ReactMapMiddleView(this.map);
this._initApp();
},
@@ -137,14 +149,11 @@ require([
},
_handlebarsPlugins: function() {
- Handlebars.registerHelper(
- 'firstLetter',
- function(text) {
- return text.charAt(0).toUpperCase();
- }
- );
+ Handlebars.registerHelper('firstLetter', function(text) {
+ return text.charAt(0).toUpperCase();
+ });
- Handlebars.registerHelper('ifCond', function (v1, operator, v2, options) {
+ Handlebars.registerHelper('ifCond', function(v1, operator, v2, options) {
switch (operator) {
case '==':
return v1 == v2 ? options.fn(this) : options.inverse(this);
@@ -172,7 +181,7 @@ require([
_googleMapsHelper: function() {
if (!google.maps.Polygon.prototype.getBounds) {
- google.maps.Polygon.prototype.getBounds = function () {
+ google.maps.Polygon.prototype.getBounds = function() {
var bounds = new google.maps.LatLngBounds();
var paths = this.getPaths();
var path;
diff --git a/app/assets/javascripts/map/helpers/layersHelper.js b/app/assets/javascripts/map/helpers/layersHelper.js
index 632d1d9c7a..cfdc191d55 100644
--- a/app/assets/javascripts/map/helpers/layersHelper.js
+++ b/app/assets/javascripts/map/helpers/layersHelper.js
@@ -177,9 +177,7 @@ define(
'map/views/layers/HaitiWatershedLayer',
'map/views/layers/EcuUserProtectedAreasLayer',
'map/views/layers/BolUserFireFrequencyLayer',
- // high resolution maps
- 'map/views/layers/SentinelLayer',
- 'map/views/layers/SentinelTilesLayer',
+ 'map/views/layers/RecentImageryLayer',
// Layer dialog templates
// 'text!templates/dialogs/loss_dialog.handlebars',
// Layers timelines
@@ -366,9 +364,7 @@ define(
HaitiWatershedLayer,
EcuUserProtectedAreasLayer,
BolUserFireFrequencyLayer,
- // highres layers
- SentinelLayer,
- SentinelTilesLayer,
+ RecentImageryLayer,
// Layer dialog templates
// loss_dialog,
// Layer timelines
@@ -777,11 +773,8 @@ define(
usa_forest_ownership: {
view: UsaForestOwnershipLayer
},
- highres: {
- view: SentinelLayer
- },
sentinel_tiles: {
- view: SentinelTilesLayer
+ view: RecentImageryLayer
},
guyra: {
view: GuyraLayer,
diff --git a/app/assets/javascripts/map/models/LayerSpecModel.js b/app/assets/javascripts/map/models/LayerSpecModel.js
index 81f41fadab..44f8275e66 100644
--- a/app/assets/javascripts/map/models/LayerSpecModel.js
+++ b/app/assets/javascripts/map/models/LayerSpecModel.js
@@ -207,10 +207,7 @@ define(['underscore', 'backbone'], function(_, Backbone) {
* @return {object} layers
*/
positionizer: function(layers) {
- var layerOrder = _.intersection(
- this.layerOrder,
- _.pluck(layers, 'slug')
- );
+ var layerOrder = _.intersection(this.layerOrder, _.pluck(layers, 'slug'));
_.each(
layerOrder,
_.bind(function(slug, i) {
@@ -293,10 +290,9 @@ define(['underscore', 'backbone'], function(_, Backbone) {
var category = this.get(categoryName);
if (category) {
categories.push(
- _.sortBy(
- this.positionizer(category),
- function(layer){ return layer.position; }
- ).reverse()
+ _.sortBy(this.positionizer(category), function(layer) {
+ return layer.position;
+ }).reverse()
);
}
}, this)
diff --git a/app/assets/javascripts/map/presenters/ReactMapMiddlePresenter.js b/app/assets/javascripts/map/presenters/ReactMapMiddlePresenter.js
new file mode 100644
index 0000000000..c207767aa0
--- /dev/null
+++ b/app/assets/javascripts/map/presenters/ReactMapMiddlePresenter.js
@@ -0,0 +1,113 @@
+/* eslint-disable */
+define(
+ [
+ 'underscore',
+ 'mps',
+ 'map/presenters/PresenterClass',
+ 'map/services/LayerSpecService'
+ ],
+ function(_, mps, PresenterClass, layerSpecService) {
+ 'use strict';
+
+ var StatusModel = Backbone.Model.extend({
+ defaults: {
+ layers: [],
+ recentImagery: null
+ }
+ });
+
+ var ReactMapMiddlePresenter = PresenterClass.extend({
+ init: function(view) {
+ this.view = view;
+ this.status = new StatusModel();
+ this._super();
+ mps.publish('Place/register', [this]);
+ },
+
+ _subscriptions: [
+ {
+ 'Place/go': function(place) {
+ this.status.set('layerSpec', place.layerSpec);
+ this.status.set('recentImagery', place.params.recentImagery);
+ var isRecentImageryActivated = !!this.status
+ .get('layerSpec')
+ .getLayer({ slug: 'sentinel_tiles' });
+ if (
+ isRecentImageryActivated &&
+ !!this.status.get('recentImagery')
+ ) {
+ this.view.fillParams(
+ JSON.parse(atob(place.params.recentImagery))
+ );
+ }
+ }
+ },
+ {
+ 'LayerNav/change': function(layerSpec) {
+ this.status.set('layerSpec', layerSpec);
+ var isRecentImageryActivated = !!this.status
+ .get('layerSpec')
+ .getLayer({ slug: 'sentinel_tiles' });
+
+ if (isRecentImageryActivated) {
+ this.setRecentImagery(this.view.getParams());
+ }
+ }
+ },
+ {
+ 'Layer/add': function(slug) {
+ if (slug === 'sentinel_tiles') {
+ window.dispatchEvent(new Event('isRecentImageryActivated'));
+ }
+ }
+ },
+ {
+ 'ReactMap/zoom-go-back': function(slug) {
+ mps.publish('Map/set-zoom', [this.view.previousZoom]);
+ }
+ }
+ ],
+
+ toggleLayer: function(layerSlug) {
+ var where = [{ slug: layerSlug }];
+ layerSpecService.toggle(
+ where,
+ _.bind(function(layerSpec) {
+ mps.publish('LayerNav/change', [layerSpec]);
+ mps.publish('Place/update', [{ go: false }]);
+ }, this)
+ );
+ },
+
+ updateLayer: function(name, params) {
+ this.setRecentImagery(this.view.getParams());
+ mps.publish('Layer/update', [name]);
+ },
+
+ setRecentImagery: function(value) {
+ if (!!value) {
+ value = btoa(JSON.stringify(value));
+ }
+
+ this.status.set('recentImagery', value);
+ this.publishRecentImagery();
+ },
+
+ publishRecentImagery: function() {
+ mps.publish('Place/update', [{ go: false }]);
+ },
+
+ getPlaceParams: function() {
+ return {
+ recentImagery: this.status.get('recentImagery')
+ };
+ },
+
+ notificate: function(id) {
+ mps.publish('Notification/open', [id]);
+ }
+ });
+
+ return ReactMapMiddlePresenter;
+ }
+);
diff --git a/app/assets/javascripts/map/presenters/SentinelTilesLayerPresenter.js b/app/assets/javascripts/map/presenters/SentinelTilesLayerPresenter.js
deleted file mode 100644
index 9c848b11e8..0000000000
--- a/app/assets/javascripts/map/presenters/SentinelTilesLayerPresenter.js
+++ /dev/null
@@ -1,35 +0,0 @@
-define([
- 'mps', 'backbone', 'moment', 'map/presenters/PresenterClass'
-], function(mps, Backbone, moment, PresenterClass) {
-
- 'use strict';
-
- var StatusModel = Backbone.Model.extend({});
-
- var SentinelTilesLayerPresenter = PresenterClass.extend({
-
- init: function(view) {
- this.view = view;
- this._super();
-
- this.status = new StatusModel();
- },
-
- _subscriptions: [{
- 'SentinelTiles/update': function(params) {
- if (params !== null) {
- params = JSON.parse(atob(params));
-
- this.view.setCurrentDate([
- moment.utc(params.mindate),
- moment.utc(params.maxdate)
- ]);
- }
- }
- }]
-
- });
-
- return SentinelTilesLayerPresenter;
-
-});
diff --git a/app/assets/javascripts/map/presenters/analysis/AnalysisResultsNewPresenter.js b/app/assets/javascripts/map/presenters/analysis/AnalysisResultsNewPresenter.js
index 5ea4c78f6f..e47315bdb4 100644
--- a/app/assets/javascripts/map/presenters/analysis/AnalysisResultsNewPresenter.js
+++ b/app/assets/javascripts/map/presenters/analysis/AnalysisResultsNewPresenter.js
@@ -94,13 +94,14 @@ define(
getRegions: function() {
var iso = this.status.get('iso');
- CountryService.getRegionsList({ iso: iso.country })
- .then(function(results) {
+ CountryService.getRegionsList({ iso: iso.country }).then(
+ function(results) {
this.status.set({
regions: results
});
this.view.render();
- }.bind(this));
+ }.bind(this)
+ );
},
getSubRegions: function() {
@@ -109,12 +110,14 @@ define(
CountryService.getSubRegionsList({
iso: iso.country,
region: iso.region
- }).then(function(results) {
- this.status.set({
- subRegions: results
- });
- this.view.render();
- }.bind(this));
+ }).then(
+ function(results) {
+ this.status.set({
+ subRegions: results
+ });
+ this.view.render();
+ }.bind(this)
+ );
},
/**
diff --git a/app/assets/javascripts/map/presenters/analysis/AnalysisResultsPresenter.js b/app/assets/javascripts/map/presenters/analysis/AnalysisResultsPresenter.js
index fe7ac32190..2a2600d07c 100644
--- a/app/assets/javascripts/map/presenters/analysis/AnalysisResultsPresenter.js
+++ b/app/assets/javascripts/map/presenters/analysis/AnalysisResultsPresenter.js
@@ -435,4 +435,4 @@ define(
return AnalysisResultsPresenter;
}
-);
+);
\ No newline at end of file
diff --git a/app/assets/javascripts/map/presenters/layers/BiodiversityCompletenessLayerPresenter.js b/app/assets/javascripts/map/presenters/layers/BiodiversityCompletenessLayerPresenter.js
index 35f2c33173..9780fb96a0 100644
--- a/app/assets/javascripts/map/presenters/layers/BiodiversityCompletenessLayerPresenter.js
+++ b/app/assets/javascripts/map/presenters/layers/BiodiversityCompletenessLayerPresenter.js
@@ -27,4 +27,4 @@ define(
return BiodiversityCompletenessLayerPresenter;
}
-);
+);
\ No newline at end of file
diff --git a/app/assets/javascripts/map/presenters/layers/BiodiversityIntactnessLayerPresenter.js b/app/assets/javascripts/map/presenters/layers/BiodiversityIntactnessLayerPresenter.js
index c85ee37768..21e728dd03 100644
--- a/app/assets/javascripts/map/presenters/layers/BiodiversityIntactnessLayerPresenter.js
+++ b/app/assets/javascripts/map/presenters/layers/BiodiversityIntactnessLayerPresenter.js
@@ -27,4 +27,4 @@ define(
return BiodiversityIntactnessLayerPresenter;
}
-);
+);
\ No newline at end of file
diff --git a/app/assets/javascripts/map/presenters/tabs/AnalysisNewPresenter.js b/app/assets/javascripts/map/presenters/tabs/AnalysisNewPresenter.js
index 4e9ec8d5aa..b2a1e5a390 100644
--- a/app/assets/javascripts/map/presenters/tabs/AnalysisNewPresenter.js
+++ b/app/assets/javascripts/map/presenters/tabs/AnalysisNewPresenter.js
@@ -936,4 +936,4 @@ define(
return AnalysisNewPresenter;
}
-);
+);
\ No newline at end of file
diff --git a/app/assets/javascripts/map/services/PlaceService.js b/app/assets/javascripts/map/services/PlaceService.js
index 451b293e1d..41353f25ea 100644
--- a/app/assets/javascripts/map/services/PlaceService.js
+++ b/app/assets/javascripts/map/services/PlaceService.js
@@ -68,7 +68,7 @@ define(
var PlaceService = PresenterClass.extend({
_uriTemplate:
- '{name}{/zoom}{/lat}{/lng}{/iso}{/maptype}{/baselayers}{/sublayers}{?tab,fit_to_geom,geojson,geostore,wdpaid,begin,end,threshold,dont_analyze,hresolution,tour,subscribe,use,useid,layer_options,lang}',
+ '{name}{/zoom}{/lat}{/lng}{/iso}{/maptype}{/baselayers}{/sublayers}{?tab,fit_to_geom,geojson,geostore,wdpaid,begin,end,threshold,dont_analyze,hresolution,recentImagery,tour,subscribe,use,useid,layer_options,lang}',
/**
* Create new PlaceService with supplied Backbone.Router.
@@ -157,6 +157,102 @@ define(
return decodeURIComponent(url);
},
+ /**
+ * Return standardized representation of supplied params object.
+ *
+ * @param {Object} params The params to standardize
+ * @return {Object} The standardized params.
+ */
+ _standardizeParams: function(params) {
+ var p = _.extendNonNull({}, urlDefaultsParams, params);
+ p.name = this._name;
+
+ p.baselayers = _.map(p.baselayers.split(','), function(slug) {
+ return { slug: slug };
+ });
+
+ p.sublayers = p.sublayers
+ ? _.map(p.sublayers.split(','), function(id) {
+ return { id: _.toNumber(id) };
+ })
+ : [];
+
+ p.zoom = _.toNumber(p.zoom);
+ p.lat = _.toNumber(p.lat);
+ p.lng = _.toNumber(p.lng);
+ p.iso = _.object(['country', 'region'], p.iso.split('-'));
+ p.begin = p.begin ? p.begin.format('YYYY-MM-DD') : null;
+ p.end = p.end ? p.end.format('YYYY-MM-DD') : null;
+ p.geostore = p.geostore ? p.geostore : null;
+ p.wdpaid = p.wdpaid ? _.toNumber(p.wdpaid) : null;
+ p.threshold = p.threshold ? _.toNumber(p.threshold) : null;
+ p.dont_analyze = p.dont_analyze ? p.dont_analyze : null;
+ p.subscribe_alerts = p.subscribe_alerts === 'subscribe' ? true : null;
+ p.referral = p.referral;
+ p.hresolution = p.hresolution;
+ p.recentImagery = p.recentImagery;
+ p.tour = p.tour;
+
+ if (p.layer_options) {
+ p.layer_options = p.layer_options.split(',');
+ }
+
+ return p;
+ },
+
+ /**
+ * Return formated URL representation of supplied params object based on
+ * a route name.
+ *
+ * @param {Object} params Place to standardize
+ * @return {Object} Params ready for URL
+ */
+ _destandardizeParams: function(params) {
+ var p = _.extendNonNull({}, urlDefaultsParams, params);
+ var baselayers = _.pluck(p.baselayers, 'slug');
+ p.name = this._name;
+ p.baselayers = baselayers.length > 0 ? baselayers : 'none';
+ p.sublayers = p.sublayers ? p.sublayers.join(',') : null;
+ p.zoom = String(p.zoom);
+ p.lat = p.lat.toFixed(2);
+ p.lng = p.lng.toFixed(2);
+ p.iso = _.compact(_.values(p.iso)).join('-') || 'ALL';
+ p.begin = p.begin ? p.begin.format('YYYY-MM-DD') : null;
+ p.end = p.end ? p.end.format('YYYY-MM-DD') : null;
+ p.geostore = p.geostore ? p.geostore : null;
+ p.wdpaid = p.wdpaid ? String(p.wdpaid) : null;
+ p.threshold = p.threshold ? String(p.threshold) : null;
+ p.dont_analyze = p.dont_analyze ? p.dont_analyze : null;
+ p.hresolution = p.hresolution;
+ p.recentImagery = p.recentImagery;
+ p.tour = p.tour;
+
+ if (p.layer_options) {
+ p.layer_options = p.layer_options.join(',');
+ }
+
+ where = _.union(place.params.baselayers, place.params.sublayers);
+
+ layerSpecService.toggle(
+ where,
+ _.bind(function(layerSpec) {
+ place.layerSpec = layerSpec;
+ mps.publish('Place/go', [place]);
+ }, this)
+ );
+ },
+
+ /**
+ * Return route URL for supplied route name and route params.
+ *
+ * @param {Object} params The route params
+ * @return {string} The route URL
+ */
+ _getRoute: function(params) {
+ var url = new UriTemplate(this._uriTemplate).fillFromObject(params);
+ return decodeURIComponent(url);
+ },
+
/**
* Return standardized representation of supplied params object.
*
diff --git a/app/assets/javascripts/map/services/SentinelService.js b/app/assets/javascripts/map/services/SentinelService.js
deleted file mode 100644
index 352233cdb0..0000000000
--- a/app/assets/javascripts/map/services/SentinelService.js
+++ /dev/null
@@ -1,83 +0,0 @@
-define([
- 'Class',
- 'uri',
- 'bluebird',
- 'map/services/DataService'
-], function(Class, UriTemplate, Promise, ds) {
-
- 'use strict';
-
- var GET_REQUEST_SENTINEL_TILES_ID = 'SentinelService:getTiles';
-
- var APIURL = 'https://staging-api.globalforestwatch.org/v1';
-
- var APIURLS = {
- 'getTiles': '/sentinel-tiles?lat={lat}&lon={lon}&start={start}&end={end}'
- };
-
- var SentinelService = Class.extend({
- init: function() {
- this.currentRequest = [];
- },
-
- getTiles: function(lat, lon, start, end) {
- return new Promise(function(resolve, reject) {
- var url = new UriTemplate(APIURLS.getTiles).fillFromObject({
- lat: lat,
- lon: lon,
- start: start,
- end: end
- });
-
- var requestId = GET_REQUEST_SENTINEL_TILES_ID + '_' + lat + '_' + lon;
- this.defineRequest(
- requestId,
- APIURL + url,
- { type: 'persist', duration: 1, unit: 'days' }
- );
-
- var requestConfig = {
- resourceId: requestId,
- success: function(res, status) {
- resolve(res.data, status);
- },
- error: function(errors) {
- reject(errors);
- }
- };
-
- this.abortRequest(requestId);
- this.currentRequest[requestId] = ds.request(requestConfig);
- }.bind(this));
- },
-
- defineRequest: function (id, url, cache) {
- ds.define(id, {
- cache: cache,
- url: url,
- type: 'GET',
- dataType: 'json',
- contentType: 'application/json; charset=utf-8',
- decoder: function ( data, status, xhr, success, error ) {
- if ( status === "success" ) {
- success( data, xhr );
- }
- }
- });
- },
-
- /**
- * Abort the current request if it exists.
- */
- abortRequest: function(request) {
- if (this.currentRequest && this.currentRequest[request]) {
- this.currentRequest[request].abort();
- this.currentRequest[request] = null;
- }
- }
-
- });
-
- return new SentinelService();
-
-});
diff --git a/app/assets/javascripts/map/templates/legend/sentinel_tiles.handlebars b/app/assets/javascripts/map/templates/legend/sentinel_tiles.handlebars
index d483535473..0366dab3a9 100644
--- a/app/assets/javascripts/map/templates/legend/sentinel_tiles.handlebars
+++ b/app/assets/javascripts/map/templates/legend/sentinel_tiles.handlebars
@@ -1,3 +1 @@
-
\ No newline at end of file
+
diff --git a/app/assets/javascripts/map/templates/tabs-mobile.handlebars b/app/assets/javascripts/map/templates/tabs-mobile.handlebars
index 706eafbeb3..a3e3e5327a 100644
--- a/app/assets/javascripts/map/templates/tabs-mobile.handlebars
+++ b/app/assets/javascripts/map/templates/tabs-mobile.handlebars
@@ -9,7 +9,6 @@
Global data
Country data
Basemaps
- High Resolution Imagery
diff --git a/app/assets/javascripts/map/views/LegendView.js b/app/assets/javascripts/map/views/LegendView.js
index 1bafb2ce98..d5e268e17b 100644
--- a/app/assets/javascripts/map/views/LegendView.js
+++ b/app/assets/javascripts/map/views/LegendView.js
@@ -359,23 +359,20 @@ define(
layer.title = 'Tree plantations';
}
- layer.sublayers = layers.filter(
- function(l) { return l.parent_layer === layer.slug; }
- );
+ layer.sublayers = layers.filter(function(l) {
+ return l.parent_layer === layer.slug;
+ });
var subLayers =
layer.sublayers.length &&
- layer.sublayers.reduce(
- function(state, l) {
- var slugData = {};
- slugData[l.slug] = {
- color: l.category_color,
- checked: 'checked'
- };
- return Object.assign(state, slugData);
- },
- {}
- );
+ layer.sublayers.reduce(function(state, l) {
+ var slugData = {};
+ slugData[l.slug] = {
+ color: l.category_color,
+ checked: 'checked'
+ };
+ return Object.assign(state, slugData);
+ }, {});
layer.detailsTpl = this.detailsTemplates[layer.slug](
Object.assign(
@@ -433,9 +430,7 @@ define(
if ($('.categories').filter('.-country')) {
var accordionCountry = $('.categories').filter('.-country');
if ($(accordionCountry).find('.js-tree-plantation')) {
- var selectOption = $(accordionCountry).find(
- '.js-tree-plantation'
- );
+ var selectOption = $(accordionCountry).find('.js-tree-plantation');
$(selectOption).removeClass('js-tree-plantation');
$(selectOption).addClass('js-tree-plantation-country');
}
@@ -447,7 +442,9 @@ define(
getLayersByCategory: function(layers) {
var subscriptionsAllowed = datasetsHelper.getListSubscriptionsAllowed();
- var filteredLayers = _.filter(layers, function(layer) { return !layer.parent_layer; });
+ var filteredLayers = _.filter(layers, function(layer) {
+ return !layer.parent_layer;
+ });
return _.groupBy(filteredLayers, function(layer) {
layer.allowSubscription =
layer && subscriptionsAllowed.indexOf(layer.slug) > -1;
@@ -575,8 +572,7 @@ define(
function(div) {
var $div = $(div);
var $toggle = $div.find('.onoffswitch');
- var optionSelected =
- layerOptions.indexOf($div.data('option')) > -1;
+ var optionSelected = layerOptions.indexOf($div.data('option')) > -1;
var color = $toggle.data('color') || '#F69';
if (optionSelected) {
@@ -630,6 +626,9 @@ define(
var layerSlug = $(e.currentTarget).data('slug');
this.presenter.toggleLayer(layerSlug);
this.removeSublayers(layerSlug);
+ window.dispatchEvent(
+ new CustomEvent('removeLayer', { detail: layerSlug })
+ );
},
showTooltip: function(e) {
@@ -642,7 +641,13 @@ define(
var dataSource = $(e.target).attr('data-source');
if (text != '') {
$('body').append(
- ''
+ ''
);
}
$('.tooltip-info-legend').css(
@@ -658,7 +663,7 @@ define(
},
removeSublayers: function(layerSlug) {
- var $subLayers = this.$el.find('[data-parent=\'' + layerSlug + '\']');
+ var $subLayers = this.$el.find("[data-parent='" + layerSlug + "']");
if ($subLayers.length > 0) {
var _this = this;
@@ -675,7 +680,7 @@ define(
},
hiddenSublayers: function(layerSlug) {
- var $subLayers = this.$el.find('[data-parent=\'' + layerSlug + '\']');
+ var $subLayers = this.$el.find("[data-parent='" + layerSlug + "']");
if ($subLayers.length > 0) {
var _this = this;
@@ -824,6 +829,15 @@ define(
iCount += 1;
});
}
+
+ window.dispatchEvent(
+ new CustomEvent('toogleLayerVisibility', {
+ detail: {
+ slug: layer,
+ visibility: true
+ }
+ })
+ );
},
hiddenLayer: function(e) {
@@ -872,6 +886,15 @@ define(
this.model.set('layers_status', layerArray);
this.map.overlayMapTypes.removeAt(index);
}
+
+ window.dispatchEvent(
+ new CustomEvent('toogleLayerVisibility', {
+ detail: {
+ slug: layer,
+ visibility: false
+ }
+ })
+ );
},
_getOverlayIndex: function(name) {
diff --git a/app/assets/javascripts/map/views/MapControlsView.js b/app/assets/javascripts/map/views/MapControlsView.js
index 41cbbbf0fd..26d801f9d1 100644
--- a/app/assets/javascripts/map/views/MapControlsView.js
+++ b/app/assets/javascripts/map/views/MapControlsView.js
@@ -1,201 +1,238 @@
+/* eslint-disable */
/**
* The MapControlsView view.
*
* @return MapControlsView view (extends Backbone.View)
*/
-define([
- 'underscore',
- 'handlebars',
- 'enquire',
- 'keymaster',
- 'map/presenters/MapControlsPresenter',
- 'map/views/controls/SearchboxView',
- 'map/views/controls/ToggleModulesView',
- 'views/ShareView',
- 'map/views/controls/ThresholdView',
- 'text!map/templates/mapcontrols.handlebars',
- 'text!map/templates/mapcontrols-mobile.handlebars'
-], function(_, Handlebars, enquire, keymaster, Presenter, Searchbox, ToggleModulesView, ShareView, ThresholdView, tpl, tplMobile) {
-
- 'use strict';
-
- var MapControlsModel = Backbone.Model.extend({
- defaults: {
- hidden: false,
- bounds: null
- }
- });
-
- var MapControlsView = Backbone.View.extend({
-
- el: '#module-map-controls',
-
- events: {
- 'click .zoom-in' : 'zoomIn',
- 'click .zoom-out' : 'zoomOut',
- 'click .reset-map' : 'resetMap',
- 'click .search' : 'showSearch',
- 'click .share-map' : 'shareMap',
- 'click .locate' : 'locateMeOnMap',
- 'click .toggle-modules' : 'toggleModules',
- 'click .toggle-mapcontrols' : 'toggleControls',
- 'click .toggle-legend': 'toggleLegend',
- },
-
- template: Handlebars.compile(tpl),
- templateMobile: Handlebars.compile(tplMobile),
-
- initialize: function(map) {
- _.bindAll(this,'zoomIn','zoomOut','resetMap','showSearch','shareMap','toggleModules');
- this.model = new MapControlsModel();
- this.presenter = new Presenter(this);
- this.map = map;
- enquire.register("screen and (min-width:"+window.gfw.config.GFW_MOBILE+"px)", {
- match: _.bind(function(){
- this.render(false);
- },this)
- });
- enquire.register("screen and (max-width:"+window.gfw.config.GFW_MOBILE+"px)", {
- match: _.bind(function(){
- this.render(true);
- },this)
- });
-
- this.cacheVars();
- this.setListeners();
- },
-
- cacheVars: function(){
- this.$toggleButtons = this.$el.find('.toggle-buttons');
- },
-
- setListeners: function(){
- key('f', this.showSearch);
- key('=, shift+=, plus, shift+equals', this.zoomIn);
- key('-', this.zoomOut);
- key('alt+r', this.resetMap);
- key('s', this.shareMap);
- key('h', this.toggleModules);
-
- this.model.on('change:hidden', this.toogleModule, this);
-
- google.maps.event.addListener(this.map, 'zoom_changed',
- _.bind(function() {
- this.onZoomChange();
- }, this)
- );
- },
-
- toogleModule: function(){
- if(this.model.get('hidden')){
- this.$el.addClass('hide');
- }else{
- this.$el.removeClass('hide');
+define(
+ [
+ 'underscore',
+ 'handlebars',
+ 'enquire',
+ 'keymaster',
+ 'map/presenters/MapControlsPresenter',
+ 'map/views/controls/SearchboxView',
+ 'map/views/controls/ToggleModulesView',
+ 'views/ShareView',
+ 'map/views/controls/ThresholdView',
+ 'text!map/templates/mapcontrols.handlebars',
+ 'text!map/templates/mapcontrols-mobile.handlebars'
+ ],
+ function(
+ _,
+ Handlebars,
+ enquire,
+ keymaster,
+ Presenter,
+ Searchbox,
+ ToggleModulesView,
+ ShareView,
+ ThresholdView,
+ tpl,
+ tplMobile
+ ) {
+ 'use strict';
+
+ var MapControlsModel = Backbone.Model.extend({
+ defaults: {
+ hidden: false,
+ bounds: null
}
- },
-
- render: function (mobile) {
- if (mobile) {
- this.$el.html(this.templateMobile({embedUrl: this._generateEmbedUrl()}));
- }else{
- this.$el.html(this.template({embedUrl: this._generateEmbedUrl()}));
- }
- this.initCustomViews();
- },
-
- initCustomViews: function(){
- new Searchbox(this.map);
- new ToggleModulesView();
- new ThresholdView();
- },
- /**
- * Events.
- */
- //ZOOM
- zoomIn: function() {
- this.setZoom(this.getZoom() + 1);
- },
- zoomOut: function(){
- this.setZoom(this.getZoom() - 1);
- },
- getZoom: function(){
- return this.map.getZoom();
- },
- setZoom: function(zoom){
- this.map.setZoom(zoom);
- },
- onZoomChange: function() {
- this.presenter.onZoomChange(this.map.zoom);
- },
-
- //RESET
- resetMap: function(){
- window.location = '/map'
- },
-
- //SEARCH
- showSearch: function(){
- this.presenter.showSearch();
- },
-
- //SHARE
- shareMap: function(event) {
- var shareView = new ShareView().share(event);
- $('body').append(shareView.el);
- },
-
-
-
- //RESET ZOOM
- fitBounds: function(){
- if(this.model.get('bounds')){
- this.presenter.fitBounds(this.model.get('bounds'));
- }else{
- this.setZoom(3);
+ });
+
+ var MapControlsView = Backbone.View.extend({
+ el: '#module-map-controls',
+
+ events: {
+ 'click .zoom-in': 'zoomIn',
+ 'click .zoom-out': 'zoomOut',
+ 'click .reset-map': 'resetMap',
+ 'click .search': 'showSearch',
+ 'click .share-map': 'shareMap',
+ 'click .locate': 'locateMeOnMap',
+ 'click .toggle-modules': 'toggleModules',
+ 'click .toggle-mapcontrols': 'toggleControls',
+ 'click .toggle-legend': 'toggleLegend'
+ },
+
+ template: Handlebars.compile(tpl),
+ templateMobile: Handlebars.compile(tplMobile),
+
+ initialize: function(map) {
+ _.bindAll(
+ this,
+ 'zoomIn',
+ 'zoomOut',
+ 'resetMap',
+ 'showSearch',
+ 'shareMap',
+ 'toggleModules'
+ );
+ this.model = new MapControlsModel();
+ this.presenter = new Presenter(this);
+ this.map = map;
+ enquire.register(
+ 'screen and (min-width:' + window.gfw.config.GFW_MOBILE + 'px)',
+ {
+ match: _.bind(function() {
+ this.render(false);
+ }, this)
+ }
+ );
+ enquire.register(
+ 'screen and (max-width:' + window.gfw.config.GFW_MOBILE + 'px)',
+ {
+ match: _.bind(function() {
+ this.render(true);
+ }, this)
+ }
+ );
+
+ this.cacheVars();
+ this.setListeners();
+ },
+
+ cacheVars: function() {
+ this.$toggleButtons = this.$el.find('.toggle-buttons');
+ },
+
+ setListeners: function() {
+ key('f', this.showSearch);
+ key('=, shift+=, plus, shift+equals', this.zoomIn);
+ key('-', this.zoomOut);
+ key('alt+r', this.resetMap);
+ key('s', this.shareMap);
+ key('h', this.toggleModules);
+
+ this.model.on('change:hidden', this.toogleModule, this);
+
+ google.maps.event.addListener(
+ this.map,
+ 'zoom_changed',
+ _.bind(function() {
+ this.onZoomChange();
+ }, this)
+ );
+ },
+
+ toogleModule: function() {
+ if (this.model.get('hidden')) {
+ this.$el.addClass('hide');
+ } else {
+ this.$el.removeClass('hide');
+ }
+ },
+
+ render: function(mobile) {
+ if (mobile) {
+ this.$el.html(
+ this.templateMobile({ embedUrl: this._generateEmbedUrl() })
+ );
+ } else {
+ this.$el.html(this.template({ embedUrl: this._generateEmbedUrl() }));
+ }
+ this.initCustomViews();
+ },
+
+ initCustomViews: function() {
+ new Searchbox(this.map);
+ new ToggleModulesView();
+ new ThresholdView();
+ },
+ /**
+ * Events.
+ */
+ //ZOOM
+ zoomIn: function() {
+ this.setZoom(this.getZoom() + 1);
+ },
+ zoomOut: function() {
+ this.setZoom(this.getZoom() - 1);
+ },
+ getZoom: function() {
+ return this.map.getZoom();
+ },
+ setZoom: function(zoom) {
+ this.map.setZoom(zoom);
+ },
+ onZoomChange: function() {
+ this.presenter.onZoomChange(this.map.zoom);
+ },
+
+ //RESET
+ resetMap: function() {
+ window.location = '/map';
+ },
+
+ //SEARCH
+ showSearch: function() {
+ this.presenter.showSearch();
+ },
+
+ //SHARE
+ shareMap: function(event) {
+ var shareView = new ShareView().share(event);
+ $('body').append(shareView.el);
+ },
+
+ //RESET ZOOM
+ fitBounds: function() {
+ if (this.model.get('bounds')) {
+ this.presenter.fitBounds(this.model.get('bounds'));
+ } else {
+ this.setZoom(3);
+ }
+ },
+
+ saveBounds: function(bounds) {
+ this.model.set('bounds', bounds);
+ },
+
+ // LOCATE ON MAP
+ locateMeOnMap: function() {
+ this.presenter.onAutolocate();
+ },
+
+ //TOGGLE
+ toggleModules: function(e) {
+ var $button = this.$el.find('.toggle-modules');
+ var $tooltip = $button.find('.tooltipmap');
+ $button.toggleClass('active');
+ $button.hasClass('active')
+ ? $tooltip.text('Show windows (h)')
+ : $tooltip.text('Hide windows (h)');
+ this.presenter.toggleModules($button.hasClass('active'));
+ },
+
+ removeToogleActive: function() {
+ this.$el.find('.toggle-modules').removeClass('active');
+ },
+
+ toggleControls: function(e) {
+ this.$toggleButtons.children('.toggle-button').toggleClass('hidden');
+ },
+
+ toggleLegend: function() {
+ this.presenter.openLegend();
+ },
+
+ toogleTimeline: function(toggle) {
+ this.$el.toggleClass('timeline-open', toggle);
+ window.dispatchEvent(
+ new CustomEvent('timelineToogle', { detail: toggle })
+ );
+ },
+
+ _generateEmbedUrl: function() {
+ return (
+ window.location.origin +
+ '/embed' +
+ window.location.pathname +
+ window.location.search
+ );
}
- },
-
- saveBounds: function(bounds){
- this.model.set('bounds', bounds);
- },
-
-
- // LOCATE ON MAP
- locateMeOnMap: function(){
- this.presenter.onAutolocate();
- },
-
-
- //TOGGLE
- toggleModules: function(e){
- var $button = this.$el.find('.toggle-modules');
- var $tooltip = $button.find('.tooltipmap');
- $button.toggleClass('active');
- ($button.hasClass('active')) ? $tooltip.text('Show windows (h)') : $tooltip.text('Hide windows (h)');
- this.presenter.toggleModules($button.hasClass('active'))
- },
-
- removeToogleActive: function() {
- this.$el.find('.toggle-modules').removeClass('active');
- },
-
- toggleControls: function(e){
- this.$toggleButtons.children('.toggle-button').toggleClass('hidden');
- },
-
- toggleLegend: function(){
- this.presenter.openLegend();
- },
-
- toogleTimeline: function(toggle){
- this.$el.toggleClass('timeline-open',toggle);
- },
-
- _generateEmbedUrl: function() {
- return window.location.origin + '/embed' + window.location.pathname + window.location.search;
- }
- });
-
- return MapControlsView;
+ });
-});
+ return MapControlsView;
+ }
+);
diff --git a/app/assets/javascripts/map/views/MapView.js b/app/assets/javascripts/map/views/MapView.js
index 6785ebaa19..c742b52021 100644
--- a/app/assets/javascripts/map/views/MapView.js
+++ b/app/assets/javascripts/map/views/MapView.js
@@ -285,6 +285,7 @@ define(
].view(layer, options, this.map));
layerView.addLayer(layer.position, _addNext);
+ mps.publish('Layer/add', [layer.slug]);
}
},
@@ -693,4 +694,4 @@ define(
return MapView;
}
-);
+);
\ No newline at end of file
diff --git a/app/assets/javascripts/map/views/ReactMapMiddleView.js b/app/assets/javascripts/map/views/ReactMapMiddleView.js
new file mode 100644
index 0000000000..aa17ff93f5
--- /dev/null
+++ b/app/assets/javascripts/map/views/ReactMapMiddleView.js
@@ -0,0 +1,75 @@
+/* eslint-disable */
+define(
+ [
+ 'underscore',
+ 'handlebars',
+ 'enquire',
+ 'moment',
+ 'mps',
+ 'cookie',
+ 'picker',
+ 'pickadate',
+ 'map/presenters/ReactMapMiddlePresenter'
+ ],
+ function(
+ _,
+ Handlebars,
+ enquire,
+ moment,
+ mps,
+ Cookies,
+ picker,
+ pickadate,
+ Presenter
+ ) {
+ 'use strict';
+
+ var SelectedDates = Backbone.Model.extend({});
+
+ var ReactMapMiddleView = Backbone.View.extend({
+ el: '#react-map',
+
+ initialize: function(map) {
+ this.presenter = new Presenter(this);
+ this.map = map;
+ this.previousZoom;
+ this.selectedDates = new SelectedDates({
+ startDateUC: moment().format('DD-MM-YYYY'),
+ endDateUC: moment()
+ .subtract(3, 'month')
+ .format('DD-MM-YYYY')
+ });
+ this.params = {};
+ },
+
+ toggleLayer: function(slug, params) {
+ this.params = params;
+ this.presenter.toggleLayer(slug);
+ },
+
+ updateLayer: function(slug, params) {
+ this.params = params;
+ this.presenter.updateLayer(slug);
+ },
+
+ showZoomAlert: function(alert, currentZoom) {
+ this.previousZoom = currentZoom;
+ this.showAlertNotification(alert);
+ },
+
+ showAlertNotification: function(alert) {
+ this.presenter.notificate(alert);
+ },
+
+ getParams: function(e) {
+ return this.params;
+ },
+
+ fillParams: function(params) {
+ this.params = params;
+ }
+ });
+
+ return ReactMapMiddleView;
+ }
+);
diff --git a/app/assets/javascripts/map/views/layers/GladLayer.js b/app/assets/javascripts/map/views/layers/GladLayer.js
index 6af6854e8e..e5617b58a6 100644
--- a/app/assets/javascripts/map/views/layers/GladLayer.js
+++ b/app/assets/javascripts/map/views/layers/GladLayer.js
@@ -162,4 +162,4 @@ define(
return GladLayer;
}
-);
+);
\ No newline at end of file
diff --git a/app/assets/javascripts/map/views/layers/GlobalLandCoverLayer.js b/app/assets/javascripts/map/views/layers/GlobalLandCoverLayer.js
index efc3096c4a..706213ef63 100644
--- a/app/assets/javascripts/map/views/layers/GlobalLandCoverLayer.js
+++ b/app/assets/javascripts/map/views/layers/GlobalLandCoverLayer.js
@@ -20,4 +20,4 @@ define([
return GlobalLandCoverLayer;
-});
+});
\ No newline at end of file
diff --git a/app/assets/javascripts/map/views/layers/MexLandCoverLayer.js b/app/assets/javascripts/map/views/layers/MexLandCoverLayer.js
index 40dad11091..467c5f3663 100644
--- a/app/assets/javascripts/map/views/layers/MexLandCoverLayer.js
+++ b/app/assets/javascripts/map/views/layers/MexLandCoverLayer.js
@@ -20,4 +20,4 @@ define([
return MexLandCoverLayer;
-});
+});
\ No newline at end of file
diff --git a/app/assets/javascripts/map/views/layers/PantropicalLayer.js b/app/assets/javascripts/map/views/layers/PantropicalLayer.js
index 8eca5149df..77b8011668 100644
--- a/app/assets/javascripts/map/views/layers/PantropicalLayer.js
+++ b/app/assets/javascripts/map/views/layers/PantropicalLayer.js
@@ -85,4 +85,4 @@ define([
return PantropicalLayer;
-});
+});
\ No newline at end of file
diff --git a/app/assets/javascripts/map/views/layers/Places2WatchLayer.js b/app/assets/javascripts/map/views/layers/Places2WatchLayer.js
index 9b6d97e948..48c7c06512 100644
--- a/app/assets/javascripts/map/views/layers/Places2WatchLayer.js
+++ b/app/assets/javascripts/map/views/layers/Places2WatchLayer.js
@@ -81,4 +81,4 @@ define(
return Places2WatchLayer;
}
-);
+);
\ No newline at end of file
diff --git a/app/assets/javascripts/map/views/layers/ProtectedAreasLayer.js b/app/assets/javascripts/map/views/layers/ProtectedAreasLayer.js
index 06698bfdeb..4c83d4960f 100644
--- a/app/assets/javascripts/map/views/layers/ProtectedAreasLayer.js
+++ b/app/assets/javascripts/map/views/layers/ProtectedAreasLayer.js
@@ -24,4 +24,4 @@ define([
return ProtectedAreasLayer;
-});
+});
\ No newline at end of file
diff --git a/app/assets/javascripts/map/views/layers/RaisgLayer.js b/app/assets/javascripts/map/views/layers/RaisgLayer.js
index f90c6517c7..8a05a165dc 100644
--- a/app/assets/javascripts/map/views/layers/RaisgLayer.js
+++ b/app/assets/javascripts/map/views/layers/RaisgLayer.js
@@ -47,4 +47,4 @@ define([
return RaisgLayer;
-});
+});
\ No newline at end of file
diff --git a/app/assets/javascripts/map/views/layers/RaisgMiningLayer.js b/app/assets/javascripts/map/views/layers/RaisgMiningLayer.js
index 1a4bd4d4d1..7b276b5a9a 100644
--- a/app/assets/javascripts/map/views/layers/RaisgMiningLayer.js
+++ b/app/assets/javascripts/map/views/layers/RaisgMiningLayer.js
@@ -46,4 +46,4 @@ define([
return RaisgMiningLayer;
-});
+});
\ No newline at end of file
diff --git a/app/assets/javascripts/map/views/layers/RecentImageryLayer.js b/app/assets/javascripts/map/views/layers/RecentImageryLayer.js
new file mode 100644
index 0000000000..39bb16642b
--- /dev/null
+++ b/app/assets/javascripts/map/views/layers/RecentImageryLayer.js
@@ -0,0 +1,66 @@
+/* eslint-disable */
+define(['mps', 'moment', 'uri', 'abstract/layer/ImageLayerClass'], function(
+ mps,
+ moment,
+ UriTemplate,
+ ImageLayerClass
+) {
+ 'use strict';
+
+ var RecentImageryLayer = ImageLayerClass.extend({
+ options: {
+ dataMaxZoom: 14
+ },
+
+ init: function(layer, options, map) {
+ this._super(layer, options, map);
+ this.currentDate = [
+ !!options.currentDate && !!options.currentDate[0]
+ ? moment.utc(options.currentDate[0])
+ : moment.utc().subtract(4, 'month'),
+ !!options.currentDate && !!options.currentDate[1]
+ ? moment.utc(options.currentDate[1])
+ : moment.utc()
+ ];
+ },
+
+ _getParams: function() {
+ var params = {};
+ if (
+ window.location.search.contains('recentImagery=') &&
+ window.location.search.indexOf(
+ '=',
+ window.location.search.indexOf('recentImagery=') + 11
+ ) !== -1
+ ) {
+ var params_new_url = {};
+ var parts = location.search.substring(1).split('&');
+ for (var i = 0; i < parts.length; i++) {
+ var nv = parts[i].split('=');
+ if (!nv[0]) continue;
+ params_new_url[nv[0]] = nv[1] || true;
+ }
+ params = JSON.parse(atob(params_new_url.recentImagery));
+ }
+
+ return params;
+ },
+
+ _getUrl: function(x, y, z, params) {
+ if (params.urlTemplate) {
+ return new UriTemplate(params.urlTemplate).fillFromObject({
+ x: x,
+ y: y,
+ z: z
+ });
+ }
+ return null;
+ },
+
+ setCurrentDate: function(date) {
+ this.currentDate = date;
+ }
+ });
+
+ return RecentImageryLayer;
+});
diff --git a/app/assets/javascripts/map/views/layers/SentinelLayer.js b/app/assets/javascripts/map/views/layers/SentinelLayer.js
deleted file mode 100644
index 10de191636..0000000000
--- a/app/assets/javascripts/map/views/layers/SentinelLayer.js
+++ /dev/null
@@ -1,350 +0,0 @@
-/**
- * The Sentinel layer module for use on canvas.
- *
- * @return SentinelLayer class (extends CartoDBLayerClass)
- */
-define([
- 'abstract/layer/ImageLayerClass',
- 'uri',
- 'moment',
- 'map/views/layers/CustomInfowindow',
-
-], function(ImageLayerClass, UriTemplate, moment, CustomInfowindow) {
-
- 'use strict';
-
- var SENTINEL_URL = 'https://services.sentinel-hub.com/v1/{provider}/9f85dfae-bf90-4638-81d3-dc9925dd5b26/';
- var LANDSAT_URL = 'https://services-uswest2.sentinel-hub.com/v1/{provider}/0e8c6cde-ff77-4e38-aba8-33b171896972/';
- var TILE_SIZE = 256;
- var MAX_ZOOM = 9;
-
- var TILES_PARAMS = '?SERVICE=WMS&REQUEST=GetMap&LAYERS=TRUE_COLOR&BBOX={bbox}&MAXCC={cloud}&CLOUDCORRECTION=none&WIDTH=512&HEIGHT=512&FORMAT=image/png&TIME={mindate}/{maxdate}&CRS=CRS:84&TRANSPARENT=TRUE&PRIORITY=mostRecent';
-
- var METADATA_PARAMS = '?service=WFS&version=2.0.0&request=GetFeature&time={mindate}/{maxdate}&typenames=TILE&maxfeatures=1&srsname=CRS:84&LAYERS=TRUE_COLOR&MAXCC={cloud}&CLOUDCORRECTION=none&&bbox={bbox}&PRIORITY=mostRecent&outputformat=application/json';
-
- var SentinelLayer = ImageLayerClass.extend({
- options: {
- dataMaxZoom: {
- 'rgb': 14,
- 'ndvi': 13,
- 'evi': 13,
- 'ndwi': 13,
- 'false-color-nir' : 13
- },
- infowindowImagelayer: true
- },
-
- init: function(layer, options, map) {
- this._super(layer, options, map);
- this.addEvents();
- },
-
- _getParams: function() {
- var params = {};
- if (window.location.search.contains('hresolution=') && window.location.search.indexOf('=', window.location.search.indexOf('hresolution=') + 11) !== -1) {
- var params_new_url = {};
- var parts = location.search.substring(1).split('&');
- for (var i = 0; i < parts.length; i++) {
- var nv = parts[i].split('=');
- if (!nv[0]) continue;
- params_new_url[nv[0]] = nv[1] || true;
- }
- params = JSON.parse(atob(params_new_url.hresolution));
- }
- else if (!!sessionStorage.getItem('high-resolution')) {
- params = JSON.parse(atob(sessionStorage.getItem('high-resolution')));
- }
-
- return {
- 'color_filter': params.color_filter || 'rgb',
- 'cloud': params.cloud || '100',
- 'mindate': params.mindate || '2000-09-01',
- 'maxdate': params.maxdate || '2015-09-01',
- 'sensor_platform' : params.sensor_platform || 'landsat-8'
- }
- },
-
- calcBboxFromXY: function(x, y, z) {
- var proj = this.map.getProjection();
- var tileSize = TILE_SIZE / Math.pow(2,z);
- var tileBounds = new google.maps.LatLngBounds(
- proj.fromPointToLatLng(new google.maps.Point(x*tileSize, (y+1)*tileSize)),
- proj.fromPointToLatLng(new google.maps.Point((x+1)*tileSize, y*tileSize))
- );
- var parsedB = tileBounds.toJSON();
- return [parsedB.west, parsedB.north, parsedB.east, parsedB.south].join(',');
- },
-
-
- _getUrlTemplateBySensor: function(sensor) {
- this.sensor = sensor;
- return sensor === 'sentinel-2' ? SENTINEL_URL
- : LANDSAT_URL;
- },
-
- _getUrl: function(x, y, z, params) {
- var urlTemplate = this._getUrlTemplateBySensor(params.sensor_platform) + TILES_PARAMS;
- var urlParams = {
- sat: params.color_filter,
- cloud: params.cloud,
- mindate: params.mindate,
- maxdate: params.maxdate,
- bbox: this.calcBboxFromXY(x, y, z),
- provider: 'wms'
- };
-
- return new UriTemplate(urlTemplate).fillFromObject(urlParams);
- },
-
- _getInfoWindowUrl: function(params) {
- var urlTemplate = this._getUrlTemplateBySensor(params.sensor_platform) + METADATA_PARAMS;
- return new UriTemplate(urlTemplate).fillFromObject({
- mindate: params.mindate,
- maxdate: params.maxdate,
- bbox: params.bbox,
- cloud: params.cloud,
- provider: 'wfs'
- });
- },
-
- _getBoundsUrl: function(params) {
- this.clear();
-
- var urlTemplate = this._getUrlTemplateBySensor(params.sensor_platform);
- return new UriTemplate(this.options[urlTemplate]).fillFromObject({
- geo: params.geo,
- cloud: params.cloud,
- mindate: moment(params.mindate).format("YYYY-MM-DD"),
- maxdate: moment(params.maxdate).format("YYYY-MM-DD"),
- tileddate: params.tileddate,
- sensor_platform: params.sensor_platform,
- provider: 'wms'
- });
- },
-
-
- // TILES
- getTile: function(coord, zoom, ownerDocument) {
- if(zoom < MAX_ZOOM) {
- return ownerDocument.createElement('div');
- }
-
- var zsteps = this._getZoomSteps(zoom);
- var srcX = TILE_SIZE * (coord.x % Math.pow(2, zsteps));
- var srcY = TILE_SIZE * (coord.y % Math.pow(2, zsteps));
- var widthandheight = (zsteps > 0) ? TILE_SIZE * Math.pow(2, zsteps) + 'px' : this.tileSize.width + 'px';
-
- var url = this._getUrl.apply(this,
- this._getTileCoords(coord.x, coord.y, zoom,this._getParams()));
-
- // Image to render
- var image = new Image();
- image.src = url;
- image.className += this.name;
- image.style.position = 'absolute';
- image.style.top = -srcY + 'px';
- image.style.left = -srcX + 'px';
- image.style.width = '100%';
- image.style.height = '100%';
-
- // Loader
- var loader = ownerDocument.createElement('div');
- loader.className += 'loader spinner start';
- loader.style.position = 'absolute';
- loader.style.top = '50%';
- loader.style.left = '50%';
- loader.style.border = '4px solid #FFF';
- loader.style.borderRadius = '50%';
- loader.style.borderTopColor = '#555';
-
- // Wwrap the loader and the image
- var div = ownerDocument.createElement('div');
- div.appendChild(image);
- div.appendChild(loader);
- div.style.width = widthandheight;
- div.style.height = widthandheight;
- div.style.position = 'relative';
- div.style.overflow = 'hidden';
- div.className += this.name;
-
- image.onload = function() {
- div.removeChild(loader);
- };
-
- image.onerror = function() {
- div.removeChild(loader)
- this.style.display = 'none';
- };
-
- return div;
- },
-
- _getTileCoords: function(x, y, z, params) {
- var maxZoom = this.options.dataMaxZoom[params['color_filter']];
- if (z > maxZoom) {
- x = Math.floor(x / (Math.pow(2, z - maxZoom)));
- y = Math.floor(y / (Math.pow(2, z - maxZoom)));
- z = maxZoom;
- } else {
- y = (y > Math.pow(2, z) ? y % Math.pow(2, z) : y);
- if (x >= Math.pow(2, z)) {
- x = x % Math.pow(2, z);
- } else if (x < 0) {
- x = Math.pow(2, z) - Math.abs(x);
- }
- }
-
- return [x, y, z, params];
- },
-
-
- // INFOWINDOW
- setInfoWindow: function (_data, event) {
- var data = _data;
- if (!!data) {
- var infoWindowOptions = {
- offset: [0, 100],
- infowindowData: {
- acquired: moment.utc(data['date'], 'YYYY-MM-DD').format("MMMM Do, YYYY"),
- sensor_platform: this.sensor,
- cloud_coverage: (data['cloudCoverPercentage']) ? Math.ceil( data['cloudCoverPercentage'] * 10) / 10 : '0'
- }
- }
- this.infowindow = new CustomInfowindow(event.latLng, this.map, infoWindowOptions);
- }
- },
-
- removeInfoWindow: function() {
- if(this.infowindow) {
- this.infowindow.remove();
- }
- },
-
-
-
- // MAP EVENTS
- addEvents: function() {
- this.clickevent = google.maps.event.addListener(this.map, "click", this.onClickEvent.bind(this));
- },
-
- clearEvents: function() {
- google.maps.event.removeListener(this.clickevent);
- },
-
- onClickEvent: function(event) {
- if(this.map.getZoom() >= MAX_ZOOM) {
- // Set options to get the url of the api
- var bounds = this.getBoundsFromLatLng(event.latLng);
- var options = _.extend({}, this._getParams(), {
- lng: event.latLng.lng(),
- lat: event.latLng.lat(),
- bbox: bounds
- });
- var url = this._getInfoWindowUrl(options);
-
- $.get(url)
- .done(function(data) {
- this.clear();
-
- var feature = data.features[0];
-
- if (feature) {
- this.drawMultipolygon(feature.geometry);
- this.setInfoWindow(feature.properties, event);
- }
- }.bind(this));
- }
- },
-
- getBoundsFromLatLng: function(latLng) {
- var offsetX = TILE_SIZE;
- var offsetY = TILE_SIZE;
-
- var tileSize = TILE_SIZE / Math.pow(2, this.map.getZoom());
-
- var point1 = this.map.getProjection().fromLatLngToPoint(latLng);
- var point2 = new google.maps.Point(offsetX / Math.pow(2, this.map.getZoom()),
- offsetY / Math.pow(2, this.map.getZoom())
- );
- var newLat = this.map.getProjection().fromPointToLatLng(new google.maps.Point(
- point1.x + (tileSize / 2),
- point1.y + (tileSize / 2)
- ));
-
- return latLng.lng() + ',' + latLng.lat() + ',' + newLat.lng() + ',' + newLat.lat();
- },
-
-
-
- // HELPERS
- setStyle: function() {
- this.map.data.setStyle(function(feature){
- return ({
- editable: false,
- strokeWeight: 2,
- fillOpacity: 0,
- fillColor: '#FFF',
- strokeColor: '#FF6633'
- });
- });
- },
-
- drawMultipolygon: function(geom) {
- var multipolygon_todraw = {
- type: "Feature",
- geometry: geom
- }
-
- this.multipolygon = this.map.data.addGeoJson(multipolygon_todraw)[0];
- if (this.listener) {
- google.maps.event.removeListener(this.listener, 'click');
- }
- this.listener = this.map.data.addListener("click", function(e){
- google.maps.event.trigger(this.map, 'click', e);
- }.bind(this));
- this.setStyle();
- },
-
- getBoundsPolygon: function() {
- var bounds = this.map.getBounds();
- if (!!bounds) {
- var nlat = bounds.getNorthEast().lat(),
- nlng = bounds.getNorthEast().lng(),
- slat = bounds.getSouthWest().lat(),
- slng = bounds.getSouthWest().lng();
-
- // Define the LngLat coordinates for the polygon.
- var boundsJson = {
- "type": "Polygon",
- "coordinates":[[
- [slng,nlat],
- [nlng,nlat],
- [nlng,slat],
- [slng,slat],
- [slng,nlat]
- ]]
- }
- return JSON.stringify(boundsJson);
- }
- return null;
- },
-
- _getZoomSteps: function(z) {
- var params = this._getParams();
- return z - this.options.dataMaxZoom[params['color_filter']];
- },
-
- clear: function() {
- this.removeMultipolygon();
- this.removeInfoWindow();
- },
-
- removeLayer: function() {
- this.clear();
- this.clearEvents();
- }
- });
-
- return SentinelLayer;
-
-});
diff --git a/app/assets/javascripts/map/views/layers/SentinelTilesLayer.js b/app/assets/javascripts/map/views/layers/SentinelTilesLayer.js
deleted file mode 100644
index abf85afb43..0000000000
--- a/app/assets/javascripts/map/views/layers/SentinelTilesLayer.js
+++ /dev/null
@@ -1,57 +0,0 @@
-define([
- 'mps',
- 'moment',
- 'abstract/layer/ImageLayerClass',
- 'map/services/SentinelService',
- 'map/presenters/SentinelTilesLayerPresenter'
-], function(
- mps,
- moment,
- ImageLayerClass,
- SentinelService,
- Presenter
-) {
-
- 'use strict';
-
- var SentinelTilesLayer = ImageLayerClass.extend({
-
- options: {
- dataMaxZoom: 14
- },
-
- init: function(layer, options, map) {
- this.presenter = new Presenter(this);
- this._super(layer, options, map);
- this.currentDate = [
- (!!options.currentDate && !!options.currentDate[0]) ?
- moment.utc(options.currentDate[0]) : moment.utc().subtract(4, 'month'),
- (!!options.currentDate && !!options.currentDate[1]) ?
- moment.utc(options.currentDate[1]) : moment.utc(),
- ];
- },
-
- _getLayer: function() {
- return new Promise(function(resolve) {
- SentinelService.getTiles(
- this.map.getCenter().lng(),
- this.map.getCenter().lat(),
- this.currentDate[0].format('YYYY-MM-DD'),
- this.currentDate[1].format('YYYY-MM-DD'))
- .then(function(response) {
- this.options.urlTemplate = response.attributes.url_image;
- resolve(this);
- }.bind(this));
-
- }.bind(this));
- },
-
- setCurrentDate: function (date) {
- this.currentDate = date;
- }
-
- });
-
- return SentinelTilesLayer;
-
-});
diff --git a/app/assets/javascripts/map/views/layers/Terra_iLayer.js b/app/assets/javascripts/map/views/layers/Terra_iLayer.js
index 5a58011b16..d90d5a5ded 100644
--- a/app/assets/javascripts/map/views/layers/Terra_iLayer.js
+++ b/app/assets/javascripts/map/views/layers/Terra_iLayer.js
@@ -20,4 +20,4 @@ define([
return Terra_iLayer;
-});
+});
\ No newline at end of file
diff --git a/app/assets/javascripts/map/views/layers/TerraiCanvasLayer.js b/app/assets/javascripts/map/views/layers/TerraiCanvasLayer.js
index 7d59f7a9f4..2cfe3cd468 100644
--- a/app/assets/javascripts/map/views/layers/TerraiCanvasLayer.js
+++ b/app/assets/javascripts/map/views/layers/TerraiCanvasLayer.js
@@ -131,4 +131,4 @@ define([
return TerraiCanvasLayer;
-});
+});
\ No newline at end of file
diff --git a/app/assets/javascripts/map/views/layers/UsaForestOwnershipLayer.js b/app/assets/javascripts/map/views/layers/UsaForestOwnershipLayer.js
index 9663d37758..2b2a0451e7 100644
--- a/app/assets/javascripts/map/views/layers/UsaForestOwnershipLayer.js
+++ b/app/assets/javascripts/map/views/layers/UsaForestOwnershipLayer.js
@@ -20,4 +20,4 @@ define([
return UsaForestOwnershipLayer;
-});
+});
\ No newline at end of file
diff --git a/app/assets/javascripts/map/views/maptypes/darkMaptype.js b/app/assets/javascripts/map/views/maptypes/darkMaptype.js
index e2aa2b5a78..1c51e4009b 100644
--- a/app/assets/javascripts/map/views/maptypes/darkMaptype.js
+++ b/app/assets/javascripts/map/views/maptypes/darkMaptype.js
@@ -23,4 +23,3 @@ define([], function () {
return DarkMaptype;
});
-
diff --git a/app/assets/javascripts/map/views/maptypes/positronMaptype.js b/app/assets/javascripts/map/views/maptypes/positronMaptype.js
index eb3d18c2ea..6167381d6a 100644
--- a/app/assets/javascripts/map/views/maptypes/positronMaptype.js
+++ b/app/assets/javascripts/map/views/maptypes/positronMaptype.js
@@ -23,4 +23,3 @@ define([], function () {
return PositronMaptype;
});
-
diff --git a/app/assets/javascripts/map/views/tabs/AnalysisView.js b/app/assets/javascripts/map/views/tabs/AnalysisView.js
index 19e62a21f3..c5e96be45a 100644
--- a/app/assets/javascripts/map/views/tabs/AnalysisView.js
+++ b/app/assets/javascripts/map/views/tabs/AnalysisView.js
@@ -466,7 +466,7 @@ define([
* Star drawing manager and add an overlaycomplete
* listener.
*/
- _startDrawingManager: function() {
+ _startDrawingManager: function() {
this.presenter.deleteMultiPoligon();
this.model.set('is_drawing', true);
this.drawingManager = new google.maps.drawing.DrawingManager({
@@ -770,4 +770,4 @@ define([
});
return AnalysisView;
-});
+});
\ No newline at end of file
diff --git a/app/assets/javascripts/map/views/tabs/BasemapsView.js b/app/assets/javascripts/map/views/tabs/BasemapsView.js
index 9500b57d68..a1c01e6827 100644
--- a/app/assets/javascripts/map/views/tabs/BasemapsView.js
+++ b/app/assets/javascripts/map/views/tabs/BasemapsView.js
@@ -105,4 +105,4 @@ define([
return BasemapsView;
-});
+});
\ No newline at end of file
diff --git a/app/assets/javascripts/static/ContributeDataView.js b/app/assets/javascripts/static/ContributeDataView.js
index 1bb56e02f6..a194b45fb0 100644
--- a/app/assets/javascripts/static/ContributeDataView.js
+++ b/app/assets/javascripts/static/ContributeDataView.js
@@ -245,7 +245,7 @@ define([
* - onChangeInput
* - onSubmitContribution
*/
- onChangeFieldset: function(e) {
+ onChangeFieldset: function(e) {
var fieldset = this.$form.find('input[name="data_show"]:checked').data('fieldset');
// Hide/open current filedset
this.$fieldsets.toggleClass('-active', false);
@@ -267,7 +267,7 @@ define([
},
- onChangeUploadType: function() {
+ onChangeUploadType: function() {
var uploadtype = this.$form.find('input[name="data_uploadtype"]:checked').data('uploadtype');
// Hide/open current filedset
this.$fieldFileUploadType.toggleClass('-active', false);
@@ -332,4 +332,4 @@ define([
return ContributeDataView;
-});
+});
\ No newline at end of file
diff --git a/app/assets/javascripts/stories/routers/StoriesRouter.js b/app/assets/javascripts/stories/routers/StoriesRouter.js
index 157c886c9e..3ce90cc755 100644
--- a/app/assets/javascripts/stories/routers/StoriesRouter.js
+++ b/app/assets/javascripts/stories/routers/StoriesRouter.js
@@ -140,4 +140,4 @@ define(
return StoriesRouter;
}
-);
+);
\ No newline at end of file
diff --git a/app/assets/javascripts/stories/utilities/story.js b/app/assets/javascripts/stories/utilities/story.js
index ac586a8742..067bf7fbc4 100644
--- a/app/assets/javascripts/stories/utilities/story.js
+++ b/app/assets/javascripts/stories/utilities/story.js
@@ -20,4 +20,4 @@ define([
}
};
-});
+});
\ No newline at end of file
diff --git a/app/assets/javascripts/stories/views/StoriesNewView.js b/app/assets/javascripts/stories/views/StoriesNewView.js
index 0923b58903..4bc821ddce 100644
--- a/app/assets/javascripts/stories/views/StoriesNewView.js
+++ b/app/assets/javascripts/stories/views/StoriesNewView.js
@@ -177,7 +177,7 @@ define([
if (!!vidID) {
$thumb.find('.inner_box').css('background-image','url('+ vidID +')');
$thumb.data('orderid', media.attributes.embedUrl);
- } else {
+ } else {
var videos = this.story.get('media').filter( function(model) {
return !!model.get('embedUrl')
});
@@ -459,7 +459,7 @@ define([
target.parentNode.insertBefore(this.sourceDrag, target.nextSibling);
}
- var orderedArray = _.map(this.$('.sortable'), function(sort) {
+ var orderedArray = _.map(this.$('.sortable'), function(sort) {
return $(sort).data('orderid');
})
@@ -592,4 +592,4 @@ define([
return StoriesNewView;
-});
+});
\ No newline at end of file
diff --git a/app/assets/javascripts/stories/views/StoriesShowView.js b/app/assets/javascripts/stories/views/StoriesShowView.js
index d22a95acdf..05ebb8443d 100644
--- a/app/assets/javascripts/stories/views/StoriesShowView.js
+++ b/app/assets/javascripts/stories/views/StoriesShowView.js
@@ -120,4 +120,4 @@ define([
return StoriesShowView;
-});
+});
\ No newline at end of file
diff --git a/app/assets/javascripts/views/SourceModalView.js b/app/assets/javascripts/views/SourceModalView.js
index 161f9f6d1f..3699980827 100644
--- a/app/assets/javascripts/views/SourceModalView.js
+++ b/app/assets/javascripts/views/SourceModalView.js
@@ -142,4 +142,4 @@ define([
return SourceModalView;
-});
+});
\ No newline at end of file
diff --git a/app/assets/stylesheets/map.scss b/app/assets/stylesheets/map.scss
index 173b061717..3e753f940e 100644
--- a/app/assets/stylesheets/map.scss
+++ b/app/assets/stylesheets/map.scss
@@ -1,3 +1,4 @@
+/* eslint-disable */
// Compass
@import "compass/reset";
@import "compass/css3";
@@ -29,7 +30,7 @@
@import "modules/shared";
@import "modules/buttons";
@import "modules/icons";
-@import "modules/slick";
+//@import "modules/slick";
@import "modules/notifications";
@import "modules/modal";
@import "modules/modal_new";
diff --git a/app/assets/stylesheets/modules/_shared.scss b/app/assets/stylesheets/modules/_shared.scss
index 24f0cef9cf..7d31f1e510 100644
--- a/app/assets/stylesheets/modules/_shared.scss
+++ b/app/assets/stylesheets/modules/_shared.scss
@@ -1,3 +1,4 @@
+/* eslint-disable */
// Information icon
.info-icon{
width: 14px;
diff --git a/app/assets/stylesheets/modules/map/_module-legend.scss b/app/assets/stylesheets/modules/map/_module-legend.scss
index e3da6bc7de..4c80412172 100644
--- a/app/assets/stylesheets/modules/map/_module-legend.scss
+++ b/app/assets/stylesheets/modules/map/_module-legend.scss
@@ -24,7 +24,7 @@
bottom: auto;
left: 35px;
padding: 0;
- max-height: calc(100% - 310px);
+ max-height: calc(100% - 340px);
transform: translate(0,0);
}
diff --git a/app/assets/stylesheets/modules/map/_module-tabs.scss b/app/assets/stylesheets/modules/map/_module-tabs.scss
index 4ce754f915..f2aae54664 100644
--- a/app/assets/stylesheets/modules/map/_module-tabs.scss
+++ b/app/assets/stylesheets/modules/map/_module-tabs.scss
@@ -34,7 +34,7 @@ $w-control: 44px;
position: relative;
@media (min-width: $br-mobileMap){
float: none;
- width: 33.33333%;
+ width: 50%;
&:first-child{
border-left: none;
diff --git a/app/javascript/assets/icons/dragger.svg b/app/javascript/assets/icons/dragger.svg
new file mode 100644
index 0000000000..09ed04a84c
--- /dev/null
+++ b/app/javascript/assets/icons/dragger.svg
@@ -0,0 +1,3 @@
+
+
+
\ No newline at end of file
diff --git a/app/javascript/assets/icons/satellite.svg b/app/javascript/assets/icons/satellite.svg
new file mode 100755
index 0000000000..c9c3eb34ee
--- /dev/null
+++ b/app/javascript/assets/icons/satellite.svg
@@ -0,0 +1,5 @@
+
+
+satellite
+
+
diff --git a/app/javascript/components/map/components/map-controls/map-controls-styles.scss b/app/javascript/components/map/components/map-controls/map-controls-styles.scss
index 3a876d2e2d..1215df20d1 100644
--- a/app/javascript/components/map/components/map-controls/map-controls-styles.scss
+++ b/app/javascript/components/map/components/map-controls/map-controls-styles.scss
@@ -3,10 +3,6 @@
.c-map-controls {
width: rem(38px);
- button {
- box-shadow: 0 1px 2px rgba($black, 0.25);
- }
-
button:first-child {
border-bottom: solid 1px rgba($medium-grey, 0.2);
}
diff --git a/app/javascript/components/map/components/mini-legend/mini-legend-styles.scss b/app/javascript/components/map/components/mini-legend/mini-legend-styles.scss
index b95041d495..3f7c302ecb 100644
--- a/app/javascript/components/map/components/mini-legend/mini-legend-styles.scss
+++ b/app/javascript/components/map/components/mini-legend/mini-legend-styles.scss
@@ -2,7 +2,7 @@
.c-mini-legend {
background-color: $white;
- box-shadow: 0 1px 3px 0 rgba(0, 0, 0, 0.25);
+ box-shadow: 0 1px 3px 0 rgba($black, 0.25);
padding: rem(10px);
padding-bottom: rem(5px);
position: absolute;
diff --git a/app/javascript/components/ui/button/button-component.jsx b/app/javascript/components/ui/button/button-component.jsx
index 1f239d73ff..9fa0255a21 100644
--- a/app/javascript/components/ui/button/button-component.jsx
+++ b/app/javascript/components/ui/button/button-component.jsx
@@ -21,6 +21,7 @@ const Button = props => {
className,
theme,
disabled,
+ active,
onClick,
tooltip,
trackingData,
@@ -28,7 +29,7 @@ const Button = props => {
} = props;
const classNames = `c-button ${theme || ''} ${className || ''} ${
disabled ? 'disabled' : ''
- }`;
+ } ${active ? '--active' : ''}`;
const isDeviceTouch = isTouch();
const handleClick = () => {
if (onClick) {
@@ -95,6 +96,7 @@ Button.propTypes = {
link: PropTypes.oneOfType([PropTypes.string, PropTypes.object]),
theme: PropTypes.string,
disabled: PropTypes.bool,
+ active: PropTypes.bool,
onClick: PropTypes.func,
extLink: PropTypes.string,
tooltip: PropTypes.object,
diff --git a/app/javascript/components/ui/button/themes/button-map-control.scss b/app/javascript/components/ui/button/themes/button-map-control.scss
index 043cc79577..f88a9c446e 100644
--- a/app/javascript/components/ui/button/themes/button-map-control.scss
+++ b/app/javascript/components/ui/button/themes/button-map-control.scss
@@ -1,17 +1,28 @@
@import '~styles/settings';
.theme-button-map-control {
- width: rem(38px);
+ width: auto;
+ min-width: rem(38px);
height: rem(38px);
padding: 0;
background-color: $white;
border-radius: 0;
+ box-shadow: 0 1px 2px rgba($black, 0.25);
+ font-size: 10px;
+ line-height: 10px;
+ color: $slate-light;
+
+ svg {
+ fill: $slate;
+ }
&:hover {
background-color: $grey-light;
}
- svg {
- fill: $slate;
+ &.--active {
+ svg {
+ fill: $green-gfw;
+ }
}
}
diff --git a/app/javascript/components/ui/carousel/slick.scss b/app/javascript/components/ui/carousel/slick.scss
index 21cba34b83..294da902d2 100644
--- a/app/javascript/components/ui/carousel/slick.scss
+++ b/app/javascript/components/ui/carousel/slick.scss
@@ -15,6 +15,7 @@
touch-action: pan-y;
-webkit-tap-highlight-color: transparent;
}
+
.slick-list {
position: relative;
overflow: hidden;
@@ -23,14 +24,15 @@
padding: 0;
&:focus {
- outline: none;
+ outline: none;
}
&.dragging {
- cursor: pointer;
- cursor: hand;
+ cursor: pointer;
+ cursor: hand;
}
}
+
.slick-slider .slick-track,
.slick-slider .slick-list {
-webkit-transform: translate3d(0, 0, 0);
@@ -48,54 +50,58 @@
margin-left: auto;
margin-right: auto;
- &:before,
- &:after {
- content: "";
- display: table;
+ &::before,
+ &::after {
+ content: "";
+ display: table;
}
- &:after {
- clear: both;
+ &::after {
+ clear: both;
}
.slick-loading & {
- visibility: hidden;
+ visibility: hidden;
}
}
+
.slick-slide {
+ display: none;
float: left;
height: 100%;
min-height: 1px;
+
[dir="rtl"] & {
- float: right;
+ float: right;
}
+
img {
- display: block;
+ display: block;
}
+
&.slick-loading img {
- display: none;
+ display: none;
}
- display: none;
-
&.dragging img {
- pointer-events: none;
+ pointer-events: none;
}
.slick-initialized & {
- display: block;
+ display: block;
}
.slick-loading & {
- visibility: hidden;
+ visibility: hidden;
}
.slick-vertical & {
- display: block;
- height: auto;
- border: 1px solid transparent;
+ display: block;
+ height: auto;
+ border: 1px solid transparent;
}
}
+
.slick-arrow.slick-hidden {
display: none;
}
diff --git a/app/javascript/components/ui/datepicker/datepicker-component.js b/app/javascript/components/ui/datepicker/datepicker-component.js
new file mode 100644
index 0000000000..f3312052b9
--- /dev/null
+++ b/app/javascript/components/ui/datepicker/datepicker-component.js
@@ -0,0 +1,42 @@
+import React, { PureComponent } from 'react';
+import PropTypes from 'prop-types';
+import { SingleDatePicker } from 'react-dates';
+
+import 'react-dates/initialize';
+import 'react-dates/lib/css/_datepicker.css';
+import './datepicker-styles.scss';
+
+class Datepicker extends PureComponent {
+ constructor(props) {
+ super(props);
+ this.state = {
+ focused: false
+ };
+ }
+
+ render() {
+ const { className, date, handleOnDateChange, settings } = this.props;
+ return (
+
+ {
+ handleOnDateChange(d);
+ }}
+ focused={this.state.focused}
+ onFocusChange={({ focused }) => this.setState({ focused })}
+ {...settings}
+ />
+
+ );
+ }
+}
+
+Datepicker.propTypes = {
+ className: PropTypes.string,
+ date: PropTypes.object,
+ handleOnDateChange: PropTypes.func.isRequired,
+ settings: PropTypes.object
+};
+
+export default Datepicker;
diff --git a/app/javascript/components/ui/datepicker/datepicker-styles.scss b/app/javascript/components/ui/datepicker/datepicker-styles.scss
new file mode 100644
index 0000000000..24201d794b
--- /dev/null
+++ b/app/javascript/components/ui/datepicker/datepicker-styles.scss
@@ -0,0 +1,54 @@
+@import '~styles/settings.scss';
+
+$reactDatesInputHeight: rem(50px);
+$reactDatesFangHeight: rem(10px);
+$datepickerInputHeight: rem(28px);
+$datepickerFangHeight: rem(6px);
+$datepickerPositionAdjust: rem(8px);
+
+.c-datepicker {
+ td {
+ vertical-align: middle;
+ }
+
+ .SingleDatePickerInput {
+ .DateInput_input {
+ padding: 0;
+ height: $datepickerInputHeight;
+ border: 1px solid $green-gfw;
+ border-radius: rem(25px);
+ font-size: 13px;
+ font-weight: normal;
+ text-align: center;
+ text-transform: uppercase;
+ color: $slate;
+ cursor: pointer;
+ }
+
+ .DateInput_fang {
+ height: $datepickerFangHeight;
+ transform: translateY($datepickerInputHeight - $reactDatesInputHeight + ($reactDatesFangHeight - $datepickerFangHeight) - $datepickerPositionAdjust);
+ }
+
+ .DateInput_fangStroke {
+ stroke: rgba($slate, 0.08);
+ }
+ }
+
+ .SingleDatePicker_picker {
+ transform: translateY($datepickerInputHeight - $reactDatesInputHeight - $datepickerPositionAdjust);
+ }
+
+ .DayPicker__withBorder {
+ box-shadow: $pop-box-shadow;
+ border-radius: 0;
+ }
+
+ .CalendarDay__selected,
+ .CalendarDay__selected:active,
+ .CalendarDay__selected:hover {
+ background: $green-gfw;
+ border: 1px solid $green-gfw;
+ color: $white;
+ }
+}
diff --git a/app/javascript/components/ui/datepicker/datepicker.js b/app/javascript/components/ui/datepicker/datepicker.js
new file mode 100644
index 0000000000..e99a24531f
--- /dev/null
+++ b/app/javascript/components/ui/datepicker/datepicker.js
@@ -0,0 +1,3 @@
+import Component from './datepicker-component';
+
+export default Component;
diff --git a/app/javascript/components/ui/dropdown/themes/dropdown-button.scss b/app/javascript/components/ui/dropdown/themes/dropdown-button.scss
index c511fd34d6..211a9e740c 100644
--- a/app/javascript/components/ui/dropdown/themes/dropdown-button.scss
+++ b/app/javascript/components/ui/dropdown/themes/dropdown-button.scss
@@ -30,7 +30,7 @@ $dd-button-height: rem(28px);
border-right: none;
transform-origin: 0 0;
transform: rotate(-45deg);
- top: calc(#{$dd-button-height} + 6px);
+ top: calc(#{$dd-button-height} + #{rem(11px)});
left: calc(50% - 14px);
border-bottom-color: #fff;
box-shadow: 1px -2px 3px 0 rgba(85, 85, 85, 0.1);
@@ -39,9 +39,9 @@ $dd-button-height: rem(28px);
}
.menu {
- top: calc(#{$dd-button-height} + 5px);
+ top: calc(#{$dd-button-height} + #{rem(12px)});
border: 0;
- box-shadow: 1px 1px 10px 0 rgba(85, 85, 85, 0.25);
+ box-shadow: $pop-box-shadow;
}
}
diff --git a/app/javascript/components/ui/loader/loader-component.js b/app/javascript/components/ui/loader/loader-component.js
index 34457c0cd2..0a24ec017a 100644
--- a/app/javascript/components/ui/loader/loader-component.js
+++ b/app/javascript/components/ui/loader/loader-component.js
@@ -9,7 +9,7 @@ class Loader extends PureComponent {
const { className, theme } = this.props;
return (
);
}
diff --git a/app/javascript/components/ui/loader/loader-styles.scss b/app/javascript/components/ui/loader/loader-styles.scss
index fa6193d378..0254ee7315 100644
--- a/app/javascript/components/ui/loader/loader-styles.scss
+++ b/app/javascript/components/ui/loader/loader-styles.scss
@@ -12,8 +12,7 @@
background: rgba($white, 0.5);
z-index: 1;
- .spinner {
- margin: rem(50px);
+ &__spinner {
height: rem(38px);
width: rem(38px);
animation: rotate 1s infinite cubic-bezier(0.645, 0.045, 0.355, 1);
diff --git a/app/javascript/components/ui/slider/slider-component.js b/app/javascript/components/ui/slider/slider-component.js
new file mode 100644
index 0000000000..79374e75b9
--- /dev/null
+++ b/app/javascript/components/ui/slider/slider-component.js
@@ -0,0 +1,48 @@
+import React, { PureComponent } from 'react';
+import PropTypes from 'prop-types';
+import RCSlider from 'rc-slider/lib/Slider';
+
+import 'rc-slider/assets/index.css';
+import './themes/slider-green.scss';
+
+const defaultSettings = {
+ min: 0,
+ max: 100,
+ defaultValue: 50,
+ tipFormatter: value => `${value}%`,
+ marksOnTop: false
+};
+
+class Slider extends PureComponent {
+ // eslint-disable-line react/prefer-stateless-function
+ render() {
+ const { className, settings, handleOnSliderChange } = this.props;
+ const sliderSettings = {
+ ...defaultSettings,
+ ...settings
+ };
+
+ return (
+
+ {
+ handleOnSliderChange(d);
+ }}
+ />
+
+ );
+ }
+}
+
+Slider.propTypes = {
+ className: PropTypes.string,
+ settings: PropTypes.object,
+ handleOnSliderChange: PropTypes.func.isRequired
+};
+
+export default Slider;
diff --git a/app/javascript/components/ui/slider/slider.js b/app/javascript/components/ui/slider/slider.js
new file mode 100644
index 0000000000..e3ff33c4ab
--- /dev/null
+++ b/app/javascript/components/ui/slider/slider.js
@@ -0,0 +1,3 @@
+import Component from './slider-component';
+
+export default Component;
diff --git a/app/javascript/components/ui/slider/themes/slider-green.scss b/app/javascript/components/ui/slider/themes/slider-green.scss
new file mode 100644
index 0000000000..a519abf533
--- /dev/null
+++ b/app/javascript/components/ui/slider/themes/slider-green.scss
@@ -0,0 +1,63 @@
+@import '~styles/settings';
+
+.theme-slider-green {
+ .rc-slider-rail {
+ background-color: transparent;
+ }
+
+ .rc-slider-track {
+ background-color: $green-gfw;
+ z-index: 2;
+ }
+
+ .rc-slider-dot {
+ bottom: rem(1px);
+ width: rem(2px);
+ height: rem(2px);
+ margin-left: 0;
+ background-color: #c4c4c4;
+ border: none;
+ border-radius: 0;
+ z-index: 1;
+ }
+
+ .rc-slider-handle {
+ box-shadow: 0 1px 3px 0 rgba($black, 0.29);
+ border: none;
+ border-radius: 2px;
+ z-index: 3;
+
+ &::before {
+ content: '';
+ position: absolute;
+ top: 0;
+ bottom: 0;
+ left: 0;
+ right: 0;
+ width: rem(4px);
+ height: rem(4px);
+ margin: auto;
+ background-color: $black;
+ border-radius: 100%;
+ }
+ }
+
+ .rc-slider-mark {
+ font-size: 10px;
+
+ .rc-slider-mark-text,
+ .rc-slider-mark-text-active {
+ color: $green-gfw;
+ }
+ }
+
+ &.-marks-on-top {
+ .rc-slider {
+ padding-top: rem(20px);
+ }
+
+ .rc-slider-mark {
+ top: 0;
+ }
+ }
+}
diff --git a/app/javascript/components/widgets/components/widget-header/widget-header-component.js b/app/javascript/components/widgets/components/widget-header/widget-header-component.js
index 5ed8f6d048..a966365d5c 100644
--- a/app/javascript/components/widgets/components/widget-header/widget-header-component.js
+++ b/app/javascript/components/widgets/components/widget-header/widget-header-component.js
@@ -102,6 +102,7 @@ class WidgetHeader extends PureComponent {
{settings &&
!isEmpty(options) && (
a.value)
- .reduce((iSum, value) => iSum + value);
+ .map(a => a.value)
+ .reduce((iSum, value) => iSum + value);
const itemCount = item.emissions
.map(a => a.value)
.reduce((iSum, value) => iSum + value);
diff --git a/app/javascript/components/widgets/widgets/land-cover/ranked-plantations/selectors.js b/app/javascript/components/widgets/widgets/land-cover/ranked-plantations/selectors.js
index 19f6a54d87..5a1f0b5fc9 100644
--- a/app/javascript/components/widgets/widgets/land-cover/ranked-plantations/selectors.js
+++ b/app/javascript/components/widgets/widgets/land-cover/ranked-plantations/selectors.js
@@ -23,7 +23,7 @@ const getSentences = state => state.config.sentences || null;
const getPlanationKeys = createSelector(
[getPlantations],
plantations =>
- (plantations ? Object.keys(groupBy(plantations, 'label')) : null)
+ plantations ? Object.keys(groupBy(plantations, 'label')) : null
);
export const parseData = createSelector(
diff --git a/app/javascript/packs/map.js b/app/javascript/packs/map.js
new file mode 100644
index 0000000000..6ef0e316da
--- /dev/null
+++ b/app/javascript/packs/map.js
@@ -0,0 +1,15 @@
+/* eslint no-console:0 */
+// This file is automatically compiled by Webpack, along with any other files
+// present in this directory. You're encouraged to place your actual application logic in
+// a relevant structure within app/javascript and only use these pack files to reference
+// that code so it'll be compiled.
+//
+// To reference this file, add <%= javascript_pack_tag 'application' %> to the appropriate
+// layout file, like app/views/layouts/application.html.erb
+import React from 'react';
+import ReactDOM from 'react-dom';
+import Map from 'pages/map';
+
+document.addEventListener('DOMContentLoaded', () => {
+ ReactDOM.render( , document.getElementById('react-map'));
+});
diff --git a/app/javascript/pages/map/index.js b/app/javascript/pages/map/index.js
new file mode 100644
index 0000000000..de8e0716a3
--- /dev/null
+++ b/app/javascript/pages/map/index.js
@@ -0,0 +1,32 @@
+import React from 'react';
+import { Provider } from 'react-redux';
+import { createStore, applyMiddleware, compose } from 'redux';
+import thunk from 'redux-thunk';
+import { handleActionTrack } from 'utils/analytics';
+
+import 'react-tippy/dist/tippy.css';
+import 'styles/styles.scss';
+
+import reducers from './reducers';
+import router from './router';
+
+import Root from './root';
+
+const composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose;
+const middlewares = applyMiddleware(
+ thunk,
+ router.middleware,
+ handleActionTrack
+);
+const store = createStore(
+ reducers,
+ composeEnhancers(router.enhancer, middlewares)
+);
+
+const Map = () => (
+
+
+
+);
+
+export default Map;
diff --git a/app/javascript/pages/map/recent-imagery/components/recent-imagery-settings/recent-imagery-settings-component.js b/app/javascript/pages/map/recent-imagery/components/recent-imagery-settings/recent-imagery-settings-component.js
new file mode 100644
index 0000000000..3cb4ae4160
--- /dev/null
+++ b/app/javascript/pages/map/recent-imagery/components/recent-imagery-settings/recent-imagery-settings-component.js
@@ -0,0 +1,189 @@
+import React, { PureComponent } from 'react';
+import PropTypes from 'prop-types';
+import moment from 'moment';
+
+import Icon from 'components/ui/icon';
+import Slider from 'components/ui/slider';
+import Carousel from 'components/ui/carousel';
+import Dropdown from 'components/ui/dropdown';
+import Datepicker from 'components/ui/datepicker';
+import NoContent from 'components/ui/no-content';
+import RecentImageryThumbnail from 'pages/map/recent-imagery/components/recent-imagery-thumbnail';
+
+import WEEKS from 'data/weeks.json';
+import draggerIcon from 'assets/icons/dragger.svg';
+import closeIcon from 'assets/icons/close.svg';
+import RecentImageryDrag from './recent-imagery-settings-drag';
+import './recent-imagery-settings-styles.scss';
+
+class RecentImagerySettings extends PureComponent {
+ constructor(props) {
+ super(props);
+ this.state = {
+ thumbnailsDescription: null
+ };
+ }
+
+ render() {
+ const {
+ selectedTile,
+ tiles,
+ settings: {
+ styles,
+ thumbsToShow,
+ selectedTileSource,
+ date,
+ weeks,
+ clouds
+ },
+ isDragging,
+ connectDragSource,
+ setRecentImagerySettings,
+ setRecentImageryShowSettings
+ } = this.props;
+ let opacity = 1;
+
+ if (isDragging) {
+ opacity = 0;
+ }
+
+ return connectDragSource(
+
+
+
setRecentImageryShowSettings(false)}
+ >
+
+
+
+ RECENT HI-RES SATELLITE IMAGERY
+
+
+
+ ACQUISITION DATE
+
+
+
+ setRecentImagerySettings({ weeks: option.value })
+ }
+ />
+
+ before
+
+
+ setRecentImagerySettings({ date: d.format('YYYY-MM-DD') })
+ }
+ settings={{
+ displayFormat: 'D MMM YYYY',
+ numberOfMonths: 1,
+ isOutsideRange: d => d.isAfter(moment()),
+ hideKeyboardShortcutsPanel: true,
+ noBorder: true,
+ readOnly: true
+ }}
+ />
+
+
+
+
+ MAXIMUM CLOUD COVER PERCENTAGE
+
+
`${value}%`
+ }}
+ handleOnSliderChange={d => setRecentImagerySettings({ clouds: d })}
+ />
+
+
+ {tiles.length >= 1 && (
+
+
+ {this.state.thumbnailsDescription ||
+ (selectedTile && selectedTile.description)}
+
+
thumbsToShow,
+ responsive: [
+ {
+ breakpoint: 620,
+ settings: {
+ slidesToShow: thumbsToShow - 2
+ }
+ }
+ ]
+ }}
+ >
+ {tiles.map((tile, i) => (
+
+ {
+ setRecentImagerySettings({
+ selectedTileSource: tile.id
+ });
+ }}
+ handleMouseEnter={() => {
+ this.setState({
+ thumbnailsDescription: tile.description
+ });
+ }}
+ handleMouseLeave={() => {
+ this.setState({ thumbnailsDescription: null });
+ }}
+ />
+
+ ))}
+
+
+ )}
+ {tiles.length < 1 && (
+
+ )}
+
+
+ );
+ }
+}
+
+RecentImagerySettings.propTypes = {
+ selectedTile: PropTypes.object,
+ tiles: PropTypes.array.isRequired,
+ settings: PropTypes.object.isRequired,
+ isDragging: PropTypes.bool.isRequired,
+ connectDragSource: PropTypes.func.isRequired,
+ setRecentImagerySettings: PropTypes.func.isRequired,
+ setRecentImageryShowSettings: PropTypes.func.isRequired
+};
+
+export default RecentImageryDrag(RecentImagerySettings);
diff --git a/app/javascript/pages/map/recent-imagery/components/recent-imagery-settings/recent-imagery-settings-drag.js b/app/javascript/pages/map/recent-imagery/components/recent-imagery-settings/recent-imagery-settings-drag.js
new file mode 100644
index 0000000000..59eab62203
--- /dev/null
+++ b/app/javascript/pages/map/recent-imagery/components/recent-imagery-settings/recent-imagery-settings-drag.js
@@ -0,0 +1,13 @@
+import { DragSource } from 'react-dnd';
+
+const dragSource = {
+ beginDrag(props) {
+ const { settings: { styles } } = props;
+ return styles;
+ }
+};
+
+export default DragSource('box', dragSource, (connect, monitor) => ({
+ connectDragSource: connect.dragSource(),
+ isDragging: monitor.isDragging()
+}));
diff --git a/app/javascript/pages/map/recent-imagery/components/recent-imagery-settings/recent-imagery-settings-styles.scss b/app/javascript/pages/map/recent-imagery/components/recent-imagery-settings/recent-imagery-settings-styles.scss
new file mode 100644
index 0000000000..3073bad6e0
--- /dev/null
+++ b/app/javascript/pages/map/recent-imagery/components/recent-imagery-settings/recent-imagery-settings-styles.scss
@@ -0,0 +1,161 @@
+@import '~styles/settings.scss';
+
+.c-recent-imagery-settings {
+ position: absolute;
+ width: calc(100% - #{rem(20px)});
+ max-width: rem(600px);
+ padding: rem(25px) 0 rem(22px) 0;
+ transform: translateX(-50%);
+ background-color: $white;
+ box-shadow: 0 1px 3px 0 rgba($black, 0.25);
+ z-index: 1001;
+
+ .dragger-icon {
+ display: none;
+ position: absolute;
+ top: rem(9px);
+ left: rem(10px);
+ width: rem(6px);
+ height: rem(10px);
+ fill: $slate;
+ }
+
+ .close-btn {
+ position: absolute;
+ top: 0;
+ right: 0;
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ width: rem(30px);
+ height: rem(30px);
+ cursor: pointer;
+
+ svg {
+ width: rem(9px);
+ height: rem(10px);
+ fill: $slate;
+ }
+ }
+
+ &__title {
+ padding-left: rem(20px);
+ margin-bottom: rem(11px);
+ font-size: 13px;
+ font-weight: 500;
+ line-height: 21px;
+ color: $slate;
+ }
+
+ &__dates {
+ position: relative;
+ display: inline-block;
+ width: 100%;
+ padding: 0 rem(20px);
+ vertical-align: top;
+ z-index: 4;
+
+ &__title {
+ margin-bottom: rem(10px);
+ }
+
+ &__title,
+ &__before {
+ font-size: 11px;
+ font-weight: 500;
+ color: $warm-grey;
+ }
+
+ &__buttons {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+
+ .c-dropdown {
+ margin-top: rem(1px);
+ }
+ }
+ }
+
+ &__clouds {
+ position: relative;
+ display: inline-block;
+ width: 100%;
+ padding: 0 rem(20px);
+ margin-top: rem(11px);
+ vertical-align: top;
+
+ &__title {
+ font-size: 11px;
+ font-weight: 500;
+ color: $warm-grey;
+ margin-bottom: rem(10px);
+ }
+
+ .c-slider {
+ padding: 0 rem(5px);
+ }
+ }
+
+ &__thumbnails {
+ position: relative;
+ width: 100%;
+ min-height: rem(146px);
+ padding-top: rem(14px);
+ margin-top: rem(20px);
+ border-top: 1px solid rgba(#9e9e9e, 0.3);
+ z-index: 1;
+
+ &__description {
+ padding-left: rem(22px);
+ margin-bottom: rem(18px);
+ font-size: 12px;
+ font-weight: 500;
+ line-height: 13px;
+ color: $slate;
+ }
+
+ .c-carousel {
+ margin: 0;
+
+ .slick-slide {
+ padding: 0 rem(5.5px);
+ }
+
+ .slick-arrow {
+ top: calc(50% - #{rem(20px)});
+ }
+
+ .slick-prev {
+ left: rem(20px);
+ }
+
+ .slick-next {
+ right: rem(20px);
+ }
+
+ .slick-track {
+ min-height: rem(100px);
+ }
+ }
+ }
+
+ &__empty-thumbnails {
+ padding-top: rem(11px);
+ }
+
+ @media screen and (min-width: rem(620px)) {
+ .dragger-icon {
+ display: block;
+ }
+
+ &__dates {
+ width: 50%;
+ }
+
+ &__clouds {
+ width: 50%;
+ margin-top: 0;
+ }
+ }
+}
diff --git a/app/javascript/pages/map/recent-imagery/components/recent-imagery-settings/recent-imagery-settings.js b/app/javascript/pages/map/recent-imagery/components/recent-imagery-settings/recent-imagery-settings.js
new file mode 100644
index 0000000000..b3179b2039
--- /dev/null
+++ b/app/javascript/pages/map/recent-imagery/components/recent-imagery-settings/recent-imagery-settings.js
@@ -0,0 +1,3 @@
+import Component from './recent-imagery-settings-component';
+
+export default Component;
diff --git a/app/javascript/pages/map/recent-imagery/components/recent-imagery-thumbnail/recent-imagery-thumbnail-component.js b/app/javascript/pages/map/recent-imagery/components/recent-imagery-thumbnail/recent-imagery-thumbnail-component.js
new file mode 100644
index 0000000000..e521b5c785
--- /dev/null
+++ b/app/javascript/pages/map/recent-imagery/components/recent-imagery-thumbnail/recent-imagery-thumbnail-component.js
@@ -0,0 +1,48 @@
+import React, { PureComponent } from 'react';
+import PropTypes from 'prop-types';
+
+import Loader from 'components/ui/loader';
+
+import './recent-imagery-thumbnail-styles.scss';
+
+class RecentImageryThumbnail extends PureComponent {
+ render() {
+ const {
+ id,
+ tile,
+ selected,
+ handleClick,
+ handleMouseEnter,
+ handleMouseLeave
+ } = this.props;
+
+ return (
+
+ {!tile.thumbnail && }
+
+ );
+ }
+}
+
+RecentImageryThumbnail.propTypes = {
+ id: PropTypes.number.isRequired,
+ tile: PropTypes.object.isRequired,
+ selected: PropTypes.bool.isRequired,
+ handleClick: PropTypes.func.isRequired,
+ handleMouseEnter: PropTypes.func,
+ handleMouseLeave: PropTypes.func
+};
+
+export default RecentImageryThumbnail;
diff --git a/app/javascript/pages/map/recent-imagery/components/recent-imagery-thumbnail/recent-imagery-thumbnail-styles.scss b/app/javascript/pages/map/recent-imagery/components/recent-imagery-thumbnail/recent-imagery-thumbnail-styles.scss
new file mode 100644
index 0000000000..43ed12304f
--- /dev/null
+++ b/app/javascript/pages/map/recent-imagery/components/recent-imagery-thumbnail/recent-imagery-thumbnail-styles.scss
@@ -0,0 +1,31 @@
+@import '~styles/settings.scss';
+
+.c-recent-imagery-thumbnail {
+ position: relative;
+ width: 100%;
+ height: rem(100px);
+ background-color: $border;
+ background-size: cover;
+ background-position: center center;
+ background-repeat: no-repeat;
+ cursor: pointer;
+ outline: none;
+
+ &::before {
+ content: '';
+ position: absolute;
+ top: 0;
+ left: 0;
+ width: 100%;
+ height: 100%;
+ border: 4px solid transparent;
+ }
+
+ &:hover::before {
+ border-color: $slate;
+ }
+
+ &--selected::before {
+ border-color: $green-gfw !important;
+ }
+}
diff --git a/app/javascript/pages/map/recent-imagery/components/recent-imagery-thumbnail/recent-imagery-thumbnail.js b/app/javascript/pages/map/recent-imagery/components/recent-imagery-thumbnail/recent-imagery-thumbnail.js
new file mode 100644
index 0000000000..8bf465133d
--- /dev/null
+++ b/app/javascript/pages/map/recent-imagery/components/recent-imagery-thumbnail/recent-imagery-thumbnail.js
@@ -0,0 +1,3 @@
+import Component from './recent-imagery-thumbnail-component';
+
+export default Component;
diff --git a/app/javascript/pages/map/recent-imagery/recent-imagery-actions.js b/app/javascript/pages/map/recent-imagery/recent-imagery-actions.js
new file mode 100644
index 0000000000..a830755af0
--- /dev/null
+++ b/app/javascript/pages/map/recent-imagery/recent-imagery-actions.js
@@ -0,0 +1,153 @@
+import { createAction } from 'redux-actions';
+import { createThunkAction } from 'utils/redux';
+import axios, { CancelToken } from 'axios';
+import findIndex from 'lodash/findIndex';
+
+import { getRecentTiles, getTiles, getThumbs } from 'services/recent-imagery';
+import { initialState } from './recent-imagery-reducers';
+
+const toogleRecentImagery = createAction('toogleRecentImagery');
+const setVisible = createAction('setVisible');
+const setTimelineFlag = createAction('setTimelineFlag');
+const setRecentImageryData = createAction('setRecentImageryData');
+const setRecentImageryDataStatus = createAction('setRecentImageryDataStatus');
+const setRecentImagerySettings = createAction('setRecentImagerySettings');
+const setRecentImageryShowSettings = createAction(
+ 'setRecentImageryShowSettings'
+);
+
+const getData = createThunkAction('getData', params => dispatch => {
+ if (this.getDataSource) {
+ this.getDataSource.cancel();
+ }
+ this.getDataSource = CancelToken.source();
+
+ getRecentTiles({ ...params, token: this.getDataSource.token })
+ .then(response => {
+ if (response.data.data.tiles) {
+ const { clouds } = initialState.settings;
+ const { source } = response.data.data.tiles[0].attributes;
+ const cloud_score = Math.round(
+ response.data.data.tiles[0].attributes.cloud_score
+ );
+
+ dispatch(
+ setRecentImageryData({
+ data: response.data.data,
+ dataStatus: {
+ haveAllData: false,
+ requestedTiles: 0
+ },
+ settings: {
+ selectedTileSource: source,
+ clouds: cloud_score > clouds ? cloud_score : clouds
+ }
+ })
+ );
+ }
+ })
+ .catch(error => {
+ console.info(error);
+ });
+});
+
+const getMoreTiles = createThunkAction(
+ 'getMoreTiles',
+ params => (dispatch, state) => {
+ if (this.getMoreTilesSource) {
+ this.getMoreTilesSource.cancel();
+ }
+ this.getMoreTilesSource = CancelToken.source();
+ const { sources, dataStatus } = params;
+
+ axios
+ .all([
+ getTiles({ sources, token: this.getMoreTilesSource.token }),
+ getThumbs({ sources, token: this.getMoreTilesSource.token })
+ ])
+ .then(
+ axios.spread((getTilesResponse, getThumbsResponse) => {
+ if (
+ getTilesResponse.data.data &&
+ getTilesResponse.data.data.attributes.length &&
+ getThumbsResponse.data.data &&
+ getThumbsResponse.data.data.attributes.length
+ ) {
+ const stateData = state().recentImagery.data;
+ const data = { ...stateData, tiles: stateData.tiles.slice() };
+ const tiles = getTilesResponse.data.data.attributes;
+ const thumbs = getThumbsResponse.data.data.attributes;
+ const requestedTiles = dataStatus.requestedTiles + tiles.length;
+ const haveAllData = requestedTiles === data.tiles.length;
+
+ tiles.forEach((item, i) => {
+ if (i > 0) {
+ const index = findIndex(
+ data.tiles,
+ d => d.attributes.source === item.source_id
+ );
+ if (index !== -1) {
+ data.tiles[index].attributes.tile_url = item.tile_url;
+ }
+ }
+ });
+ thumbs.forEach(item => {
+ const index = findIndex(
+ data.tiles,
+ d => d.attributes.source === item.source
+ );
+ if (index !== -1) {
+ data.tiles[index].attributes.thumbnail_url = item.thumbnail_url;
+ }
+ });
+
+ dispatch(
+ setRecentImageryData({
+ data,
+ dataStatus: {
+ haveAllData,
+ requestedTiles
+ }
+ })
+ );
+ }
+ })
+ )
+ .catch(error => {
+ dispatch(
+ setRecentImageryData({
+ dataStatus: {
+ requestFails: params.dataStatus.requestFails + 1
+ }
+ })
+ );
+ console.info(error);
+ });
+ }
+);
+
+const resetData = createThunkAction('resetData', () => dispatch => {
+ dispatch(setRecentImageryShowSettings(false));
+ dispatch(
+ setRecentImageryData({
+ data: {},
+ dataStatus: {
+ haveAllData: false,
+ requestedTiles: 0
+ }
+ })
+ );
+});
+
+export default {
+ toogleRecentImagery,
+ setVisible,
+ setTimelineFlag,
+ setRecentImageryData,
+ setRecentImageryDataStatus,
+ setRecentImagerySettings,
+ setRecentImageryShowSettings,
+ getData,
+ getMoreTiles,
+ resetData
+};
diff --git a/app/javascript/pages/map/recent-imagery/recent-imagery-component.js b/app/javascript/pages/map/recent-imagery/recent-imagery-component.js
new file mode 100644
index 0000000000..f9d632a068
--- /dev/null
+++ b/app/javascript/pages/map/recent-imagery/recent-imagery-component.js
@@ -0,0 +1,74 @@
+import React, { PureComponent } from 'react';
+import PropTypes from 'prop-types';
+
+import Icon from 'components/ui/icon';
+import satelliteIcon from 'assets/icons/satellite.svg';
+import Button from 'components/ui/button';
+import RecentImagerySettings from './components/recent-imagery-settings';
+
+import './recent-imagery-styles.scss';
+
+class RecentImagery extends PureComponent {
+ render() {
+ const {
+ active,
+ showSettings,
+ isTimelineOpen,
+ tile,
+ allTiles,
+ settings,
+ canDrop,
+ connectDropTarget,
+ toogleRecentImagery,
+ setRecentImagerySettings,
+ setRecentImageryShowSettings
+ } = this.props;
+
+ return connectDropTarget(
+
+ toogleRecentImagery()}
+ >
+
+
+ Recent Imagery
+
+
+ {showSettings && (
+
+ )}
+
+ );
+ }
+}
+
+RecentImagery.propTypes = {
+ active: PropTypes.bool.isRequired,
+ showSettings: PropTypes.bool.isRequired,
+ isTimelineOpen: PropTypes.bool.isRequired,
+ tile: PropTypes.object,
+ allTiles: PropTypes.array.isRequired,
+ settings: PropTypes.object.isRequired,
+ connectDropTarget: PropTypes.func.isRequired,
+ canDrop: PropTypes.bool.isRequired,
+ toogleRecentImagery: PropTypes.func.isRequired,
+ setRecentImagerySettings: PropTypes.func.isRequired,
+ setRecentImageryShowSettings: PropTypes.func.isRequired
+};
+
+export default RecentImagery;
diff --git a/app/javascript/pages/map/recent-imagery/recent-imagery-drag.js b/app/javascript/pages/map/recent-imagery/recent-imagery-drag.js
new file mode 100644
index 0000000000..b2a8eab7b4
--- /dev/null
+++ b/app/javascript/pages/map/recent-imagery/recent-imagery-drag.js
@@ -0,0 +1,17 @@
+import { DropTarget } from 'react-dnd';
+
+const dropTarget = {
+ drop(props, monitor) {
+ const { setRecentImagerySettings } = props;
+ const item = monitor.getItem();
+ const delta = monitor.getDifferenceFromInitialOffset();
+ const top = Math.round(item.top + delta.y);
+ const left = `calc(${item.left} + ${Math.round(delta.x)}px)`;
+ setRecentImagerySettings({ styles: { top, left } });
+ }
+};
+
+export default DropTarget('box', dropTarget, (connect, monitor) => ({
+ connectDropTarget: connect.dropTarget(),
+ canDrop: monitor.canDrop()
+}));
diff --git a/app/javascript/pages/map/recent-imagery/recent-imagery-reducers.js b/app/javascript/pages/map/recent-imagery/recent-imagery-reducers.js
new file mode 100644
index 0000000000..23fbe886b2
--- /dev/null
+++ b/app/javascript/pages/map/recent-imagery/recent-imagery-reducers.js
@@ -0,0 +1,86 @@
+export const initialState = {
+ active: false,
+ visible: false,
+ showSettings: false,
+ isTimelineOpen: false,
+ data: {},
+ dataStatus: {
+ tilesPerRequest: 6,
+ haveAllData: false,
+ requestedTiles: 0,
+ requestFails: 0
+ },
+ settings: {
+ styles: {
+ top: 90,
+ left: '50%'
+ },
+ layerSlug: 'sentinel_tiles',
+ minZoom: 9,
+ thumbsToShow: 5,
+ selectedTileSource: null,
+ date: null,
+ weeks: 13,
+ clouds: 25
+ }
+};
+
+const toogleRecentImagery = state => ({
+ ...state,
+ active: !state.active,
+ visible: !state.active
+});
+
+const setVisible = (state, { payload }) => ({
+ ...state,
+ visible: payload
+});
+
+const setTimelineFlag = (state, { payload }) => ({
+ ...state,
+ isTimelineOpen: payload
+});
+
+const setRecentImageryData = (state, { payload }) => ({
+ ...state,
+ data: payload.data ? payload.data : state.data,
+ dataStatus: {
+ ...state.dataStatus,
+ ...payload.dataStatus
+ },
+ settings: {
+ ...state.settings,
+ ...payload.settings
+ }
+});
+
+const setRecentImageryDataStatus = (state, { payload }) => ({
+ ...state,
+ dataStatus: {
+ ...state.dataStatus,
+ ...payload
+ }
+});
+
+const setRecentImagerySettings = (state, { payload }) => ({
+ ...state,
+ settings: {
+ ...state.settings,
+ ...payload
+ }
+});
+
+const setRecentImageryShowSettings = (state, { payload }) => ({
+ ...state,
+ showSettings: payload
+});
+
+export default {
+ toogleRecentImagery,
+ setVisible,
+ setTimelineFlag,
+ setRecentImageryData,
+ setRecentImageryDataStatus,
+ setRecentImagerySettings,
+ setRecentImageryShowSettings
+};
diff --git a/app/javascript/pages/map/recent-imagery/recent-imagery-selectors.js b/app/javascript/pages/map/recent-imagery/recent-imagery-selectors.js
new file mode 100644
index 0000000000..e76a61db07
--- /dev/null
+++ b/app/javascript/pages/map/recent-imagery/recent-imagery-selectors.js
@@ -0,0 +1,108 @@
+import { createSelector } from 'reselect';
+import isEmpty from 'lodash/isEmpty';
+import findIndex from 'lodash/findIndex';
+import moment from 'moment';
+import { format } from 'd3-format';
+import { sortByKey } from 'utils/data';
+
+const getData = state => state.data || null;
+const getDataStatus = state => state.dataStatus || null;
+const getSettings = state => state.settings || null;
+
+const getFilteredData = createSelector(
+ [getData, getSettings],
+ (data, settings) => {
+ if (!data || isEmpty(data)) return null;
+
+ const { clouds } = settings;
+ const dataFiltered = data.filter(
+ item => Math.round(item.attributes.cloud_score) <= clouds
+ );
+ return sortByKey(
+ dataFiltered.map(item => item.attributes),
+ 'date_time',
+ true
+ );
+ }
+);
+
+export const getAllTiles = createSelector([getFilteredData], data => {
+ if (!data || isEmpty(data)) return [];
+
+ return data.map(item => ({
+ id: item.source,
+ url: item.tile_url,
+ thumbnail: item.thumbnail_url,
+ cloudScore: item.cloud_score,
+ dateTime: item.date_time,
+ instrument: item.instrument,
+ description: `${moment(item.date_time)
+ .format('DD MMM YYYY')
+ .toUpperCase()} - ${format('.0f')(item.cloud_score)}% cloud coverage - ${
+ item.instrument
+ }`
+ }));
+});
+
+export const getTile = createSelector(
+ [getData, getSettings],
+ (data, settings) => {
+ if (!data || isEmpty(data)) return null;
+
+ const { selectedTileSource } = settings;
+ const index = findIndex(
+ data,
+ d => d.attributes.source === selectedTileSource
+ );
+
+ const selectedTile = data[index].attributes;
+ return {
+ url: selectedTile.tile_url,
+ cloudScore: selectedTile.cloud_score,
+ dateTime: selectedTile.date_time,
+ instrument: selectedTile.instrument,
+ description: `${moment(selectedTile.date_time)
+ .format('DD MMM YYYY')
+ .toUpperCase()} - ${format('.0f')(
+ selectedTile.cloud_score
+ )}% cloud coverage - ${selectedTile.instrument}`
+ };
+ }
+);
+
+export const getBounds = createSelector(
+ [getData, getSettings],
+ (data, settings) => {
+ if (!data || isEmpty(data)) return null;
+
+ const { selectedTileSource } = settings;
+ const index = findIndex(
+ data,
+ d => d.attributes.source === selectedTileSource
+ );
+
+ return data[index].attributes.bbox.geometry.coordinates;
+ }
+);
+
+export const getSources = createSelector(
+ [getData, getDataStatus],
+ (data, dataStatus) => {
+ if (!data || isEmpty(data)) return null;
+
+ const { tilesPerRequest, requestedTiles } = dataStatus;
+ return data
+ .slice(requestedTiles, requestedTiles + tilesPerRequest)
+ .map(item => ({ source: item.attributes.source }));
+ }
+);
+
+export const getDates = createSelector([getSettings], settings => {
+ const { date, weeks } = settings;
+ const currentDate = date ? moment(date) : moment();
+
+ return {
+ end: currentDate.format('YYYY-MM-DD'),
+ start: currentDate.subtract(weeks, 'weeks').format('YYYY-MM-DD')
+ };
+});
diff --git a/app/javascript/pages/map/recent-imagery/recent-imagery-styles.scss b/app/javascript/pages/map/recent-imagery/recent-imagery-styles.scss
new file mode 100644
index 0000000000..90419929ab
--- /dev/null
+++ b/app/javascript/pages/map/recent-imagery/recent-imagery-styles.scss
@@ -0,0 +1,62 @@
+@import '~styles/settings.scss';
+
+.c-recent-imagery {
+ &__button {
+ position: absolute;
+ right: rem(10px);
+ bottom: rem(238px);
+ width: rem(38px);
+ transition: transform 0.25s cubic-bezier(0.445, 0.05, 0.55, 0.95);
+ z-index: 19;
+
+ .satellite-icon {
+ width: rem(20px);
+ height: rem(20px);
+ }
+
+ span {
+ display: none;
+ text-align: left;
+ }
+
+ &--timeline-open {
+ transform: translate(0, rem(-45px));
+ }
+ }
+
+ &--dragging {
+ position: relative;
+ width: 100%;
+ height: 100vh;
+ }
+
+ @media screen and (min-width: rem(1000px)) {
+ &__button {
+ right: auto;
+ left: rem(35px);
+ bottom: rem(172px);
+ width: rem(76px);
+
+ .satellite-icon {
+ margin-right: rem(5px);
+ }
+
+ span {
+ display: inline-block;
+ }
+ }
+ }
+}
+
+.recent-imagery-infowindow {
+ padding: rem(15px);
+ font-family: $font-family-1;
+ font-size: 12px;
+ color: $white;
+ background: rgba($black, 0.6);
+
+ &__hook {
+ margin-top: rem(5px);
+ font-size: 11px;
+ }
+}
diff --git a/app/javascript/pages/map/recent-imagery/recent-imagery.js b/app/javascript/pages/map/recent-imagery/recent-imagery.js
new file mode 100644
index 0000000000..7f86197ef2
--- /dev/null
+++ b/app/javascript/pages/map/recent-imagery/recent-imagery.js
@@ -0,0 +1,300 @@
+/* eslint-disable no-undef */
+
+import { createElement, PureComponent } from 'react';
+import { connect } from 'react-redux';
+import PropTypes from 'prop-types';
+import isEqual from 'lodash/isEqual';
+
+import actions from './recent-imagery-actions';
+import reducers, { initialState } from './recent-imagery-reducers';
+import {
+ getAllTiles,
+ getTile,
+ getBounds,
+ getSources,
+ getDates
+} from './recent-imagery-selectors';
+import RecentImageryDrag from './recent-imagery-drag';
+import RecentImageryComponent from './recent-imagery-component';
+
+const mapStateToProps = ({ recentImagery }) => {
+ const {
+ active,
+ visible,
+ showSettings,
+ isTimelineOpen,
+ data,
+ dataStatus,
+ settings
+ } = recentImagery;
+ const selectorData = {
+ data: data.tiles,
+ dataStatus,
+ settings
+ };
+ return {
+ active,
+ visible,
+ showSettings,
+ isTimelineOpen,
+ dataStatus,
+ allTiles: getAllTiles(selectorData),
+ tile: getTile(selectorData),
+ bounds: getBounds(selectorData),
+ sources: getSources(selectorData),
+ dates: getDates(selectorData),
+ settings
+ };
+};
+
+class RecentImageryContainer extends PureComponent {
+ componentDidMount() {
+ this.middleView = window.App.Views.ReactMapMiddleView;
+ this.boundsPolygon = null;
+ this.boundsPolygonInfowindow = null;
+ this.activatedFromUrl = false;
+ this.removedFromUrl = false;
+ window.addEventListener('isRecentImageryActivated', () => {
+ const { active, toogleRecentImagery } = this.props;
+ if (!active) {
+ this.activatedFromUrl = true;
+ toogleRecentImagery();
+ }
+ });
+ window.addEventListener('removeLayer', e => {
+ const { active, toogleRecentImagery } = this.props;
+ if (e.detail === 'sentinel_tiles' && active) {
+ this.removedFromUrl = true;
+ toogleRecentImagery();
+ }
+ });
+ window.addEventListener('timelineToogle', e => {
+ const { setTimelineFlag } = this.props;
+ setTimelineFlag(e.detail);
+ });
+ window.addEventListener('toogleLayerVisibility', e => {
+ const { settings: { layerSlug }, setVisible } = this.props;
+ if (e.detail.slug === layerSlug) {
+ setVisible(e.detail.visibility);
+ }
+ });
+ }
+
+ componentWillReceiveProps(nextProps) {
+ const {
+ active,
+ showSettings,
+ dataStatus,
+ tile,
+ bounds,
+ sources,
+ dates,
+ settings,
+ getData,
+ getMoreTiles
+ } = nextProps;
+ const { map } = this.middleView;
+ const isNewTile =
+ tile && (!this.props.tile || tile.url !== this.props.tile.url);
+
+ if (
+ (active && active !== this.props.active) ||
+ !isEqual(settings.date, this.props.settings.date) ||
+ !isEqual(settings.weeks, this.props.settings.weeks)
+ ) {
+ getData({
+ latitude: map.getCenter().lng(),
+ longitude: map.getCenter().lat(),
+ start: dates.start,
+ end: dates.end
+ });
+ }
+ if (!active && active !== this.props.active) {
+ this.removeLayer();
+ this.removeEvents();
+ this.removeBoundsPolygon();
+ }
+ if (isNewTile) {
+ if (this.activatedFromUrl && !this.props.tile) {
+ this.updateLayer(tile.url);
+ this.setEvents();
+ } else if (!this.props.tile) {
+ this.showLayer(tile.url);
+ this.setEvents();
+ } else {
+ this.updateLayer(tile.url);
+ }
+ this.addBoundsPolygon(bounds, tile);
+ }
+ if (
+ !dataStatus.haveAllData &&
+ showSettings &&
+ (showSettings !== this.props.showSettings ||
+ dataStatus.requestedTiles !== this.props.dataStatus.requestedTiles ||
+ dataStatus.requestFails !== this.props.dataStatus.requestFails ||
+ isNewTile)
+ ) {
+ getMoreTiles({ sources, dataStatus });
+ }
+ }
+
+ setEvents() {
+ const { map } = this.middleView;
+
+ this.mapIdleEvent = map.addListener('idle', () => {
+ const { visible, dates, getData } = this.props;
+ if (visible) {
+ const needNewTile = !google.maps.geometry.poly.containsLocation(
+ map.getCenter(),
+ this.boundsPolygon
+ );
+ if (needNewTile) {
+ getData({
+ latitude: map.getCenter().lng(),
+ longitude: map.getCenter().lat(),
+ start: dates.start,
+ end: dates.end
+ });
+ }
+ }
+ });
+ }
+
+ removeEvents() {
+ google.maps.event.removeListener(this.mapIdleEvent);
+ }
+
+ showLayer(url) {
+ const { map } = this.middleView;
+ const { settings: { layerSlug, minZoom } } = this.props;
+ const zoom = map.getZoom();
+
+ this.middleView.toggleLayer(layerSlug, {
+ urlTemplate: url
+ });
+ if (zoom < minZoom) {
+ map.setZoom(minZoom);
+ this.middleView.showZoomAlert('notification-zoom-go-back', zoom);
+ }
+ }
+
+ removeLayer() {
+ const { settings: { layerSlug }, resetData } = this.props;
+ if (!this.removedFromUrl) {
+ this.middleView.toggleLayer(layerSlug);
+ }
+ this.activatedFromUrl = false;
+ this.removedFromUrl = false;
+ resetData();
+ }
+
+ updateLayer(url) {
+ const { settings: { layerSlug } } = this.props;
+ this.middleView.updateLayer(layerSlug, {
+ urlTemplate: url
+ });
+ }
+
+ addBoundsPolygon(bounds, tile) {
+ const { map } = this.middleView;
+ const { description } = tile;
+
+ if (this.boundsPolygon !== null) {
+ this.removeBoundsPolygon();
+ }
+ const coords = [];
+ bounds.forEach(item => {
+ coords.push({
+ lat: item[1],
+ lng: item[0]
+ });
+ });
+
+ this.boundsPolygon = new google.maps.Polygon({
+ paths: coords,
+ fillColor: 'transparent',
+ strokeWeight: 0
+ });
+
+ this.addBoundsPolygonEvents();
+ this.boundsPolygon.setMap(map);
+
+ if (this.boundsPolygonInfowindow !== null) {
+ this.boundsPolygonInfowindow.close();
+ }
+ this.boundsPolygonInfowindow = new google.maps.InfoWindow({
+ content: `
+
+ ${description}
+
Click to refine image
+
+ `
+ });
+ }
+
+ addBoundsPolygonEvents() {
+ const { setRecentImageryShowSettings } = this.props;
+ const { map } = this.middleView;
+ let clickTimeout = null;
+
+ google.maps.event.addListener(this.boundsPolygon, 'mouseover', () => {
+ this.boundsPolygon.setOptions({
+ strokeColor: '#000000',
+ strokeOpacity: 0.5,
+ strokeWeight: 1
+ });
+ this.boundsPolygonInfowindow.open(map);
+ });
+ google.maps.event.addListener(this.boundsPolygon, 'mouseout', () => {
+ this.boundsPolygon.setOptions({
+ strokeWeight: 0
+ });
+ this.boundsPolygonInfowindow.close();
+ });
+ google.maps.event.addListener(this.boundsPolygon, 'mousemove', e => {
+ this.boundsPolygonInfowindow.setPosition(e.latLng);
+ });
+ google.maps.event.addListener(this.boundsPolygon, 'click', () => {
+ clickTimeout = setTimeout(() => {
+ setRecentImageryShowSettings(true);
+ }, 200);
+ });
+ google.maps.event.addListener(this.boundsPolygon, 'dblclick', () => {
+ clearTimeout(clickTimeout);
+ });
+ }
+
+ removeBoundsPolygon() {
+ this.boundsPolygon.setMap(null);
+ }
+
+ render() {
+ return createElement(RecentImageryComponent, {
+ ...this.props
+ });
+ }
+}
+
+RecentImageryContainer.propTypes = {
+ active: PropTypes.bool,
+ visible: PropTypes.bool,
+ showSettings: PropTypes.bool,
+ dataStatus: PropTypes.object,
+ tile: PropTypes.object,
+ bounds: PropTypes.array,
+ sources: PropTypes.array,
+ dates: PropTypes.object,
+ settings: PropTypes.object,
+ toogleRecentImagery: PropTypes.func,
+ setVisible: PropTypes.func,
+ setTimelineFlag: PropTypes.func,
+ setRecentImageryShowSettings: PropTypes.func,
+ getData: PropTypes.func,
+ getMoreTiles: PropTypes.func,
+ resetData: PropTypes.func
+};
+
+export { actions, reducers, initialState };
+export default connect(mapStateToProps, actions)(
+ RecentImageryDrag(RecentImageryContainer)
+);
diff --git a/app/javascript/pages/map/reducers.js b/app/javascript/pages/map/reducers.js
new file mode 100644
index 0000000000..650946e42a
--- /dev/null
+++ b/app/javascript/pages/map/reducers.js
@@ -0,0 +1,27 @@
+/* eslint-disable import/first */
+import { combineReducers } from 'redux';
+import { handleActions } from 'utils/redux';
+
+// Routes
+import router from './router';
+
+// Pages
+import * as map from 'pages/map/root';
+
+const pagesReducers = {
+ root: handleActions(map)
+};
+
+// Components
+import * as recentImageryComponent from 'pages/map/recent-imagery';
+
+// Component Reducers
+const componentsReducers = {
+ recentImagery: handleActions(recentImageryComponent)
+};
+
+export default combineReducers({
+ ...pagesReducers,
+ ...componentsReducers,
+ location: router.reducer
+});
diff --git a/app/javascript/pages/map/root/root-actions.js b/app/javascript/pages/map/root/root-actions.js
new file mode 100644
index 0000000000..ed33f305d4
--- /dev/null
+++ b/app/javascript/pages/map/root/root-actions.js
@@ -0,0 +1,7 @@
+import { createAction } from 'redux-actions';
+
+const setRootLoading = createAction('setRootLoading');
+
+export default {
+ setRootLoading
+};
diff --git a/app/javascript/pages/map/root/root-component.js b/app/javascript/pages/map/root/root-component.js
new file mode 100644
index 0000000000..6cfdea289a
--- /dev/null
+++ b/app/javascript/pages/map/root/root-component.js
@@ -0,0 +1,32 @@
+import React, { PureComponent } from 'react';
+import PropTypes from 'prop-types';
+import HTML5Backend from 'react-dnd-html5-backend';
+import { DragDropContextProvider } from 'react-dnd';
+
+import RecentImagery from 'pages/map/recent-imagery';
+
+import './root-styles.scss';
+
+class Root extends PureComponent {
+ render() {
+ const { loading } = this.props;
+
+ if (loading) {
+ return null;
+ }
+
+ return (
+
+
+
+
+
+ );
+ }
+}
+
+Root.propTypes = {
+ loading: PropTypes.bool.isRequired
+};
+
+export default Root;
diff --git a/app/javascript/pages/map/root/root-reducers.js b/app/javascript/pages/map/root/root-reducers.js
new file mode 100644
index 0000000000..6145098178
--- /dev/null
+++ b/app/javascript/pages/map/root/root-reducers.js
@@ -0,0 +1,12 @@
+export const initialState = {
+ loading: true
+};
+
+const setRootLoading = (state, { payload }) => ({
+ ...state,
+ loading: payload
+});
+
+export default {
+ setRootLoading
+};
diff --git a/app/javascript/pages/map/root/root-styles.scss b/app/javascript/pages/map/root/root-styles.scss
new file mode 100644
index 0000000000..b548c098d9
--- /dev/null
+++ b/app/javascript/pages/map/root/root-styles.scss
@@ -0,0 +1,9 @@
+@import '~styles/settings.scss';
+
+.l-map {
+
+}
+
+.gm-style div div:nth-child(4) div:nth-child(4) div:nth-child(2) > div:not(.gm-style-iw) {
+ display: none;
+}
diff --git a/app/javascript/pages/map/root/root.js b/app/javascript/pages/map/root/root.js
new file mode 100644
index 0000000000..71c1d7573c
--- /dev/null
+++ b/app/javascript/pages/map/root/root.js
@@ -0,0 +1,37 @@
+import { createElement, PureComponent } from 'react';
+import { connect } from 'react-redux';
+import PropTypes from 'prop-types';
+
+import actions from './root-actions';
+import reducers, { initialState } from './root-reducers';
+import RootComponent from './root-component';
+
+const mapStateToProps = ({ root }) => {
+ const { loading } = root;
+ return {
+ loading
+ };
+};
+
+class RootContainer extends PureComponent {
+ componentDidMount() {
+ window.addEventListener('mapLoaded', () => {
+ const { setRootLoading } = this.props;
+ setRootLoading(false);
+ });
+ }
+
+ render() {
+ return createElement(RootComponent, {
+ ...this.props
+ });
+ }
+}
+
+RootContainer.propTypes = {
+ setRootLoading: PropTypes.func
+};
+
+export { actions, reducers, initialState };
+
+export default connect(mapStateToProps, actions)(RootContainer);
diff --git a/app/javascript/pages/map/router.js b/app/javascript/pages/map/router.js
new file mode 100644
index 0000000000..f085705011
--- /dev/null
+++ b/app/javascript/pages/map/router.js
@@ -0,0 +1,25 @@
+import { connectRoutes } from 'redux-first-router';
+import createHistory from 'history/createBrowserHistory';
+import queryString from 'query-string';
+import { handlePageTrack } from 'utils/analytics';
+
+const history = createHistory();
+
+export const MAP = 'location/MAP';
+
+const routeChangeThunk = (dispatch, getState) => {
+ // track page with GA
+ handlePageTrack(getState().location);
+};
+
+export const routes = {
+ [MAP]: {
+ path:
+ '/map/:zoom?/:latitude?/:longitude?/:iso?/:basemap?/:layers?/:sublayers?'
+ }
+};
+
+export default connectRoutes(history, routes, {
+ querySerializer: queryString,
+ onAfterChange: routeChangeThunk
+});
diff --git a/app/javascript/pages/sgf/section-projects/section-projects-selectors.js b/app/javascript/pages/sgf/section-projects/section-projects-selectors.js
index 0e28bdb02d..eb38064a86 100644
--- a/app/javascript/pages/sgf/section-projects/section-projects-selectors.js
+++ b/app/javascript/pages/sgf/section-projects/section-projects-selectors.js
@@ -142,4 +142,4 @@ export const getGlobeClusters = createSelector(
}));
return mapPoints;
}
-);
+);
\ No newline at end of file
diff --git a/app/javascript/services/recent-imagery.js b/app/javascript/services/recent-imagery.js
new file mode 100644
index 0000000000..3e845ecd63
--- /dev/null
+++ b/app/javascript/services/recent-imagery.js
@@ -0,0 +1,31 @@
+import axios from 'axios';
+
+const REQUEST_URL = `${process.env.GFW_API_HOST_PROD}`;
+
+const QUERIES = {
+ recentTiles:
+ '/recent-tiles?lat={latitude}&lon={longitude}&start={start}&end={end}',
+ tiles: '/recent-tiles/tiles',
+ thumbs: '/recent-tiles/thumbs'
+};
+
+export const getRecentTiles = ({ latitude, longitude, start, end, token }) => {
+ const url = `${REQUEST_URL}${QUERIES.recentTiles}`
+ .replace('{latitude}', latitude)
+ .replace('{longitude}', longitude)
+ .replace('{start}', start)
+ .replace('{end}', end);
+ return axios.get(url, { cancelToken: token });
+};
+
+export const getTiles = ({ sources, token }) =>
+ axios.post(`${REQUEST_URL}${QUERIES.tiles}`, {
+ source_data: sources,
+ cancelToken: token
+ });
+
+export const getThumbs = ({ sources, token }) =>
+ axios.post(`${REQUEST_URL}${QUERIES.thumbs}`, {
+ source_data: sources,
+ cancelToken: token
+ });
diff --git a/app/javascript/styles/base.scss b/app/javascript/styles/base.scss
index 80da472912..3329d3759f 100644
--- a/app/javascript/styles/base.scss
+++ b/app/javascript/styles/base.scss
@@ -10,3 +10,12 @@ body {
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
+
+*[draggable="true"] {
+ cursor: move;
+ cursor: grab;
+
+ &:active {
+ cursor: grabbing;
+ }
+}
diff --git a/app/javascript/styles/settings.scss b/app/javascript/styles/settings.scss
index a086938b87..da91baef4b 100644
--- a/app/javascript/styles/settings.scss
+++ b/app/javascript/styles/settings.scss
@@ -40,3 +40,6 @@ $font-color-3: $slate-dark;
$font-color-4: $green-gfw;
$font-color-5: $warm-grey;
$font-color-6: $slate-light;
+
+/* SHADOWS */
+$pop-box-shadow: 1px 1px 10px 0 rgba($slate, 0.25);
diff --git a/app/javascript/utils/analytics.js b/app/javascript/utils/analytics.js
index c7a6037c7e..30963d5f00 100644
--- a/app/javascript/utils/analytics.js
+++ b/app/javascript/utils/analytics.js
@@ -51,7 +51,7 @@ export const handleActionTrack = state => nextDispatch => action => {
// process event if available
if (event) {
const evalProp = prop =>
- (typeof prop === 'string' ? prop : prop(payload));
+ typeof prop === 'string' ? prop : prop(payload);
const eventPayload = {
...['category', 'action', 'label'].reduce(
(val, prop) => ({ ...val, [prop]: evalProp(event[prop]) }),
diff --git a/app/javascript/utils/map.js b/app/javascript/utils/map.js
new file mode 100644
index 0000000000..3e064b2440
--- /dev/null
+++ b/app/javascript/utils/map.js
@@ -0,0 +1,32 @@
+export const getPolygonCenter = polygon => {
+ const vertices = polygon.getPath();
+ const latitude = [];
+ const longitude = [];
+ for (let i = 0; i < vertices.length; i++) {
+ longitude.push(vertices.getAt(i).lng());
+ latitude.push(vertices.getAt(i).lat());
+ }
+ latitude.sort();
+ longitude.sort();
+ const lowX = latitude[0];
+ const highX = latitude[vertices.length - 1];
+ const lowY = longitude[0];
+ const highY = longitude[vertices.length - 1];
+
+ const center = new google.maps.LatLng( // eslint-disable-line
+ lowX + (highX - lowX) / 2,
+ lowY + (highY - lowY) / 2
+ );
+ const top = new google.maps.LatLng(highX, lowY + (highY - lowY) / 2); // eslint-disable-line
+ const bottom = new google.maps.LatLng(lowX, lowY + (highY - lowY) / 2); // eslint-disable-line
+ const right = new google.maps.LatLng(lowX + (highX - lowX) / 2, highY); // eslint-disable-line
+ const left = new google.maps.LatLng(lowX + (highX - lowX) / 2, lowY); // eslint-disable-line
+
+ return {
+ center,
+ top,
+ bottom,
+ left,
+ right
+ };
+};
diff --git a/app/views/map/embed.html.erb b/app/views/map/embed.html.erb
index c63594b817..ff9f5d2560 100644
--- a/app/views/map/embed.html.erb
+++ b/app/views/map/embed.html.erb
@@ -4,6 +4,9 @@
<% content_for :head do %>
<%= render 'shared/google_carto' %>
+
+ <%= javascript_pack_tag controller_name %>
+ <%= stylesheet_pack_tag controller_name %>
<% end %>
<% content_for :header do %>
@@ -24,6 +27,8 @@
<%= render 'shared/modal' %>
<%= render 'shared/notifications' %>
+
+
<%= render 'shared/icons/svg_icons_map' %>
<%= render 'shared/icons/svg_icons_applications' %>
<%= render 'shared/icons/svg_icons_layers' %>
diff --git a/app/views/map/index.html.erb b/app/views/map/index.html.erb
index 5ad3d63f5e..c49e875080 100644
--- a/app/views/map/index.html.erb
+++ b/app/views/map/index.html.erb
@@ -1,5 +1,8 @@
<% content_for :head do %>
<%= render 'shared/google_carto' %>
+
+ <%= javascript_pack_tag controller_name %>
+ <%= stylesheet_pack_tag controller_name %>
<% end %>
<% content_for :header do %>
@@ -20,6 +23,8 @@
<%= render 'shared/modal' %>
<%= render 'shared/notifications' %>
+
+
<%= render 'shared/icons/svg_icons_map' %>
<%= render 'shared/icons/svg_icons_applications' %>
<%= render 'shared/icons/svg_icons_layers' %>
diff --git a/app/views/shared/_map.html.erb b/app/views/shared/_map.html.erb
index a477fb4ecd..b21d075a4e 100644
--- a/app/views/shared/_map.html.erb
+++ b/app/views/shared/_map.html.erb
@@ -12,6 +12,7 @@
+
diff --git a/app/views/shared/_notifications.html.erb b/app/views/shared/_notifications.html.erb
index c45a5fdb96..f2d227e959 100644
--- a/app/views/shared/_notifications.html.erb
+++ b/app/views/shared/_notifications.html.erb
@@ -101,6 +101,9 @@
No imagery is available in this area for the selected time period and settings.
+
+
We are zooming the map to overlay recent satellite imagery. Cancel
+
diff --git a/app/views/shared/_sources_map.html.erb b/app/views/shared/_sources_map.html.erb
index 22d9e3955f..3230fdc610 100644
--- a/app/views/shared/_sources_map.html.erb
+++ b/app/views/shared/_sources_map.html.erb
@@ -6499,21 +6499,16 @@
- High Resolution
-
-
+ Recent Imagery
Function
- Shows the latest satellite imagery from Landsat 8 and Sentinel-2
+ Shows the latest satellite imagery from Sentinel-2
Source
- Sentinel Hub , Sinergise Ltd.
+ Google Earth Engine
Frequency of updates
@@ -6523,7 +6518,7 @@
Overview
-
This layer shows recent satellite imagery from Landsat 8 and Sentinel-2, as provided by Sentinel Hub.
+
This layer shows recent satellite imagery, as provided by Google Earth Engine, from the following sensors:
@@ -6536,14 +6531,6 @@
-
-
- USGS
- 30 m
- every 16 days
- March 2013
- Global
-
ESA
@@ -6554,13 +6541,7 @@
-
-
Click on an image to see the capture date and sensor name. The most recent image for an area is shown, though the date range can be modified in the menu. Users can also change the renderer and maximum cloud cover percentage by toggling advanced tools in the menu. The maximum cloud percentage sets the threshold for excluding cloudy images.
-
-
-
Sentinal hub Products © Copyright sentinal hub – All Rights Reserved
-
-
Learn more
+
Hover on an image to see the capture date and sensor name. The most recent image for an area is shown, click to view other images or change the time period. Users can also change the maximum cloud cover percentage in the menu, which sets the threshold for excluding cloudy images.
diff --git a/jstest/helpers/analysis_service_response.js b/jstest/helpers/analysis_service_response.js
index f02aadad41..cd42b68762 100644
--- a/jstest/helpers/analysis_service_response.js
+++ b/jstest/helpers/analysis_service_response.js
@@ -46,4 +46,4 @@ var AnalysisServiceResponse = {
"period": "Past 24 hours",
"value": 4004
}
-};
+};
\ No newline at end of file
diff --git a/jstest/helpers/api_responses.js b/jstest/helpers/api_responses.js
index 478c8b1778..46ff92b5a3 100644
--- a/jstest/helpers/api_responses.js
+++ b/jstest/helpers/api_responses.js
@@ -27,4 +27,4 @@ var ApiResponse = {
}
}
}
-};
+};
\ No newline at end of file
diff --git a/jstest/helpers/baselayers.js b/jstest/helpers/baselayers.js
index 7f975b4830..17fc01f961 100644
--- a/jstest/helpers/baselayers.js
+++ b/jstest/helpers/baselayers.js
@@ -51,4 +51,4 @@ var baselayers = {
"position": 0,
"detailsTpl": "\n
\n \n
\n\n"
}
-};
+};
\ No newline at end of file
diff --git a/jstest/helpers/layers.js b/jstest/helpers/layers.js
index 7cfee73eef..963efe70cf 100644
--- a/jstest/helpers/layers.js
+++ b/jstest/helpers/layers.js
@@ -171,4 +171,4 @@ var layers = {
"tileurl": "https://wri-01.carto.com/tiles/cod_mc_4/{Z}/{X}/{Y}.png",
"visible": true
}
-};
+};
\ No newline at end of file
diff --git a/jstest/helpers/layerspec.js b/jstest/helpers/layerspec.js
index 93f544ec7d..5383a8dfb4 100644
--- a/jstest/helpers/layerspec.js
+++ b/jstest/helpers/layerspec.js
@@ -127,4 +127,4 @@ var layerSpec = {
"visible": true
}
}
-};
+};
\ No newline at end of file
diff --git a/package.json b/package.json
index df40040843..65e9d9ecf7 100644
--- a/package.json
+++ b/package.json
@@ -113,7 +113,11 @@
"query-string": "5.0.1",
"rails-erb-loader": "5.1.0",
"raw-loader": "0.5.1",
+ "rc-slider": "^8.6.1",
"react": "15.6.1",
+ "react-dates": "^16.3.6",
+ "react-dnd": "^2.5.4",
+ "react-dnd-html5-backend": "^2.5.4",
"react-document-events": "1.3.2",
"react-dom": "15.6.1",
"react-dotdotdot": "1.1.0",
diff --git a/vendor/assets/javascripts/backbone.cartodb.js b/vendor/assets/javascripts/backbone.cartodb.js
index f06a8bd954..2c9ed207d1 100644
--- a/vendor/assets/javascripts/backbone.cartodb.js
+++ b/vendor/assets/javascripts/backbone.cartodb.js
@@ -13,198 +13,198 @@
Backbone.CartoDB = function(options, query, cache) {
- options = _.defaults(options, {
- USE_PROXY: false,
- user: ''
- });
-
- function _SQL(sql) {
- this.sql = sql;
- }
- function SQL(sql) {
- return new _SQL(sql);
+ options = _.defaults(options, {
+ USE_PROXY: false,
+ user: ''
+ });
+
+ function _SQL(sql) {
+ this.sql = sql;
+ }
+ function SQL(sql) {
+ return new _SQL(sql);
+ }
+
+ // SQL("{0} is {1}").format("CartoDB", "epic!");
+ _SQL.prototype.format = function() {
+ var str = this.sql,
+ len = arguments.length+1;
+ var safe, arg;
+ for (i=0; i < len; arg = arguments[i++]) {
+ safe = typeof arg === 'object' ? JSON.stringify(arg) : arg;
+ str = str.replace(RegExp('\\{'+(i-1)+'\\}', 'g'), safe);
}
-
- // SQL("{0} is {1}").format("CartoDB", "epic!");
- _SQL.prototype.format = function() {
- var str = this.sql,
- len = arguments.length+1;
- var safe, arg;
- for (i=0; i < len; arg = arguments[i++]) {
- safe = typeof arg === 'object' ? JSON.stringify(arg) : arg;
- str = str.replace(RegExp('\\{'+(i-1)+'\\}', 'g'), safe);
+ return str;
+ };
+
+
+ var resource_path= options.user + '.carto.com/api/v1/sql';
+ var resource_url = 'https://' + resource_path;
+
+ /**
+ * fetch sql from the server
+ *
+ * this function should be changed if you're working on node
+ *
+ */
+ query = query || function(sql, callback, proxy) {
+ var url = resource_url;
+ var crossDomain = true;
+ if(proxy) {
+ url = 'api/v0/proxy/' + resource_url;
+ crossDomain = false;
}
- return str;
- };
-
-
- var resource_path= options.user + '.carto.com/api/v1/sql';
- var resource_url = 'https://' + resource_path;
-
- /**
- * fetch sql from the server
- *
- * this function should be changed if you're working on node
- *
- */
- query = query || function(sql, callback, proxy) {
- var url = resource_url;
- var crossDomain = true;
- if(proxy) {
- url = 'api/v0/proxy/' + resource_url;
- crossDomain = false;
- }
- if(sql.length > 1500) {
- $.ajax({
- url: url,
- crossDomain: crossDomain,
- type: 'POST',
- dataType: 'json',
- data: 'q=' + encodeURIComponent(sql),
- success: callback,
- error: function(){
- if(proxy) {
- callback();
- } else {
- //try fallback
- if(USE_PROXY) {
- query(sql, callback, true);
- }
- }
+ if(sql.length > 1500) {
+ $.ajax({
+ url: url,
+ crossDomain: crossDomain,
+ type: 'POST',
+ dataType: 'json',
+ data: 'q=' + encodeURIComponent(sql),
+ success: callback,
+ error: function(){
+ if(proxy) {
+ callback();
+ } else {
+ //try fallback
+ if(USE_PROXY) {
+ query(sql, callback, true);
+ }
}
- });
+ }
+ });
+ } else {
+ // TODO: add timeout
+ $.getJSON(resource_url + '?q=' + encodeURIComponent(sql) + '&callback=?')
+ .success(callback)
+ .fail(function(){
+ callback();
+ }).complete(function() {
+ });
+ }
+ };
+
+ var dummy_cache = {
+ setItem: function(key, value) { },
+ getItem: function(key) { return null; },
+ removeItem: function(key) { }
+ };
+
+ cache = cache && dummy_cache;
+
+
+ var CartoDBModel = Backbone.Model.extend({
+
+ _create_sql: function() {
+ var where = SQL(" where {0} = '{1}'").format(
+ this.columns[this.what],
+ this.get(this.what).replace("'", "''")
+ );
+ var select = this._sql_select();
+ var sql = 'select ' + select.join(',') + ' from ' + this.table + where;
+ return sql;
+ },
+
+ _sql_select: function() {
+ var select = [];
+ for(var k in this.columns) {
+ var w = this.columns[k];
+ if(w.indexOf('ST_') !== -1 || w === "the_geom") {
+ select.push(SQL('ST_AsGeoJSON({1}) as {0}').format(k,w));
} else {
- // TODO: add timeout
- $.getJSON(resource_url + '?q=' + encodeURIComponent(sql) + '&callback=?')
- .success(callback)
- .fail(function(){
- callback();
- }).complete(function() {
- });
- }
- };
-
- var dummy_cache = {
- setItem: function(key, value) { },
- getItem: function(key) { return null; },
- removeItem: function(key) { }
- };
-
- cache = cache && dummy_cache;
-
-
- var CartoDBModel = Backbone.Model.extend({
-
- _create_sql: function() {
- var where = SQL(" where {0} = '{1}'").format(
- this.columns[this.what],
- this.get(this.what).replace("'", "''")
- );
- var select = this._sql_select();
- var sql = 'select ' + select.join(',') + ' from ' + this.table + where;
- return sql;
- },
-
- _sql_select: function() {
- var select = [];
- for(var k in this.columns) {
- var w = this.columns[k];
- if(w.indexOf('ST_') !== -1 || w === "the_geom") {
- select.push(SQL('ST_AsGeoJSON({1}) as {0}').format(k,w));
- } else {
- select.push(SQL('{1} as {0}').format(k, w));
- }
- }
- return select;
- },
-
- _parse_columns: function(row) {
- var parsed = {};
- for(var k in row) {
- var v = row[k];
- var c = this.columns[k];
- if (c.indexOf('ST_') !== -1 || c === "the_geom") {
- parsed[k] = JSON.parse(v);
- } else {
- parsed[k] = row[k];
- }
+ select.push(SQL('{1} as {0}').format(k, w));
}
- return parsed;
- },
-
- fetch: function() {
- var self = this;
- query(this._create_sql(), function(data) {
- self.set(self._parse_columns(data.rows[0]));
- });
}
- });
-
-
- /**
- * cartodb collection created from a sql composed using 'columns' and
- * 'table' attributes defined in a child class
- *
- * var C = CartoDBCollection.extend({
- * table: 'table',
- * columns: ['c1', 'c2']
- * });
- * var c = new C();
- * c.fetch();
- */
- var CartoDBCollection = Backbone.Collection.extend({
-
- _create_sql: function() {
- var tables = this.table;
- if(!_.isArray(this.table)) {
- tables = [this.table];
- }
- tables = tables.join(',');
- var select = CartoDBModel.prototype._sql_select.call(this);
- var sql = 'select ' + select.join(',') + ' from ' + this.table;
- if (this.where) {
- sql += " WHERE " + this.where;
- }
- return sql;
- },
-
- fetch: function() {
- var self = this;
- var sql = this.sql || this._create_sql();
- if(typeof(sql) === "function") {
- sql = sql.call(this);
- }
- var item = this.cache ? cache.getItem(sql): false;
- if(!item) {
- query(sql, function(data) {
- if(this.cache) {
- try {
- cache.setItem(sql, JSON.stringify(data.rows));
- } catch(e) {}
- }
- var rows;
- if(!self.sql) {
- rows = _.map(data.rows, function(r) {
- return CartoDBModel.prototype._parse_columns.call(self, r);
- });
- } else {
- rows = data.rows;
- }
- self.reset(rows);
- });
+ return select;
+ },
+
+ _parse_columns: function(row) {
+ var parsed = {};
+ for(var k in row) {
+ var v = row[k];
+ var c = this.columns[k];
+ if (c.indexOf('ST_') !== -1 || c === "the_geom") {
+ parsed[k] = JSON.parse(v);
} else {
- self.reset(JSON.parse(item));
+ parsed[k] = row[k];
}
}
+ return parsed;
+ },
+
+ fetch: function() {
+ var self = this;
+ query(this._create_sql(), function(data) {
+ self.set(self._parse_columns(data.rows[0]));
+ });
+ }
+ });
+
+
+ /**
+ * cartodb collection created from a sql composed using 'columns' and
+ * 'table' attributes defined in a child class
+ *
+ * var C = CartoDBCollection.extend({
+ * table: 'table',
+ * columns: ['c1', 'c2']
+ * });
+ * var c = new C();
+ * c.fetch();
+ */
+ var CartoDBCollection = Backbone.Collection.extend({
+
+ _create_sql: function() {
+ var tables = this.table;
+ if(!_.isArray(this.table)) {
+ tables = [this.table];
+ }
+ tables = tables.join(',');
+ var select = CartoDBModel.prototype._sql_select.call(this);
+ var sql = 'select ' + select.join(',') + ' from ' + this.table;
+ if (this.where) {
+ sql += " WHERE " + this.where;
+ }
+ return sql;
+ },
+
+ fetch: function() {
+ var self = this;
+ var sql = this.sql || this._create_sql();
+ if(typeof(sql) === "function") {
+ sql = sql.call(this);
+ }
+ var item = this.cache ? cache.getItem(sql): false;
+ if(!item) {
+ query(sql, function(data) {
+ if(this.cache) {
+ try {
+ cache.setItem(sql, JSON.stringify(data.rows));
+ } catch(e) {}
+ }
+ var rows;
+ if(!self.sql) {
+ rows = _.map(data.rows, function(r) {
+ return CartoDBModel.prototype._parse_columns.call(self, r);
+ });
+ } else {
+ rows = data.rows;
+ }
+ self.reset(rows);
+ });
+ } else {
+ self.reset(JSON.parse(item));
+ }
+ }
- });
+ });
- return {
- query: query,
- CartoDBCollection: CartoDBCollection,
- CartoDBModel: CartoDBModel,
- SQL: SQL
- };
+ return {
+ query: query,
+ CartoDBCollection: CartoDBCollection,
+ CartoDBModel: CartoDBModel,
+ SQL: SQL
+ };
-};
+};
\ No newline at end of file