Skip to content
Snippets Groups Projects
Commit c9d1b074 authored by Benoit Lavenier's avatar Benoit Lavenier
Browse files

[enh] ES settings: allow to define a google API key, for address resolution in maps

parent ef9a2b28
No related branches found
No related tags found
No related merge requests found
Showing
with 155 additions and 49 deletions
......@@ -198,6 +198,7 @@
<script src="dist/dist_js/plugins/map/js/controllers/wot-controllers.js"></script>
<script src="dist/dist_js/plugins/map/js/controllers/network-controllers.js"></script>
<script src="dist/dist_js/plugins/map/js/controllers/user-controllers.js"></script>
<script src="dist/dist_js/plugins/map/js/controllers/settings-controllers.js"></script>
<!-- RML9 plugin -->
<!--<script src="dist/dist_js/plugins/rml9/plugin-01-add_button.js"></script>-->
......
......@@ -64,4 +64,4 @@ angular.module("cesium.config", [])
"newIssueUrl": "https://github.com/duniter/cesium/issues/new?labels=bug"
})
;
\ No newline at end of file
;
......@@ -10,7 +10,7 @@ angular.module('cesium.es.geo.services', ['cesium.services', 'cesium.es.http.ser
})
.factory('esGeo', function($q, csConfig, csHttp) {
.factory('esGeo', function($rootScope, $q, csConfig, csSettings, csHttp) {
'ngInject';
var
......@@ -22,7 +22,7 @@ angular.module('cesium.es.geo.services', ['cesium.services', 'cesium.es.http.ser
searchByQuery: csHttp.get('nominatim.openstreetmap.org', 443, '/search.php?format=json')
},
google: {
apiKey: csConfig.plugins && csConfig.plugins.es && csConfig.plugins.es.googleApiKey,
apiKey: undefined,
search: csHttp.get('maps.google.com', 443, '/maps/api/geocode/json')
},
searchByIP: csHttp.get('freegeoip.net', 80, '/json/:ip')
......@@ -126,6 +126,24 @@ angular.module('cesium.es.geo.services', ['cesium.services', 'cesium.es.http.ser
});
}
// If no google api key in config: get it from settings
that.raw.google.apiKey = csConfig.plugins && csConfig.plugins.es && csConfig.plugins.es.googleApiKey;
if (!that.raw.google.apiKey) {
csSettings.ready()
.then(function() {
// Listen settings changed
function onSettingsChanged(data){
that.raw.google.enable = data.plugins && data.plugins.es && data.plugins.es.enableGoogleApi;
that.raw.google.apiKey = that.raw.google.enable && data.plugins.es.googleApiKey;
}
csSettings.api.data.on.changed($rootScope, onSettingsChanged, this);
onSettingsChanged(csSettings.data);
})
}
return {
point: {
current: getCurrentPosition,
......@@ -133,8 +151,8 @@ angular.module('cesium.es.geo.services', ['cesium.services', 'cesium.es.http.ser
searchByIP: searchPositionByIP
},
google: {
hasApiKey : function() {
return !!that.raw.google.apiKey;
isEnable: function() {
return that.raw.google.enable && that.raw.google.apiKey;
},
searchByAddress: googleSearchPositionByString
}
......
......@@ -40,7 +40,10 @@ angular.module('cesium.es.settings.services', ['cesium.services', 'cesium.es.htt
},
invitations: {
readTime: true
}
},
defaultCountry: undefined,
enableGoogleApi: false,
googleApiKey: undefined
}
}
}, {plugins: {es: csConfig.plugins && csConfig.plugins.es || {}}}),
......
......@@ -64,5 +64,10 @@
</div>
</label>
<!-- Allow extension here -->
<cs-extension-point name="common"></cs-extension-point>
</ion-content>
</ion-view>
......@@ -36,6 +36,15 @@
},
"ERROR": {
"LOCALIZE_ME_FAILED": "Impossible de récupérer votre position actuelle"
},
"SETTINGS": {
"MAP_DIVIDER": "Cartes",
"ENABLE_GOOGLE_API": "Activer les services Google API ?",
"ENABLE_GOOGLE_API_HELP": "Permet l'affichage dans la <b>carte des membres</b> des comptes ayant une adresse mais aucun positionnement GPS.",
"GOOGLE_API_KEY": "Clé d'API Google",
"BTN_GOOGLE_API": "Obtenir une clé",
"BTN_GOOGLE_API_WARNING": "nécessite d'avoir un compte Google",
"GOOGLE_API_KEY_PLACEHOLDER": "Exemple: AIzaqyAgszvWm0tM81x1sMK_ipDHBI7EowLqR7I"
}
}
}
angular.module('cesium.map.settings.controllers', ['cesium.services'])
// Configure menu items
.config(function(PluginServiceProvider, csConfig) {
'ngInject';
var enable = csConfig.plugins && csConfig.plugins.es;
if (enable) {
// Extend settings via extension points
PluginServiceProvider.extendState('app.es_settings', {
points: {
'common': {
templateUrl: "plugins/map/templates/settings/es_settings_extend.html"
}
}
});
}
})
;
......@@ -102,19 +102,8 @@ angular.module('cesium.map.wot.controllers', ['cesium.services', 'cesium.map.ser
if ($scope.loading) {
// Load the map (and init if need)
$scope.loadMap().then(function(map) {
// Load indicator
map.fire('dataloading');
// Load data
return $scope.load()
// Hide loading indicator
.then(function() {
map.fire('dataload');
});
});
$scope.loadMap()
.then($scope.load);
}
else {
// Make sur to have previous center coordinate defined in the location URL
......@@ -127,16 +116,24 @@ angular.module('cesium.map.wot.controllers', ['cesium.services', 'cesium.map.ser
return leafletData.getMap($scope.mapId).then(function(map) {
if (!$scope.map.loading) return map; // already loaded
// Add a refresh button
if (!UIUtils.screen.isSmall()) {
L.easyButton('icon ion-refresh', function(btn, map){
return $scope.load(map);
},
{position: 'topright'}
).addTo(map);
}
// Add loading control
var loadingControl = L.Control.loading({
L.Control.loading({
position: 'topright',
separate: true
});
loadingControl.addTo(map);
}).addTo(map);
// Add localize me control
var localizeMe = MapUtils.control.localizeMe();
localizeMe.addTo(map);
MapUtils.control.localizeMe()
.addTo(map);
// Add search control
var searchTip = $interpolate($templateCache.get('plugins/map/templates/wot/item_search_tooltip.html'));
......@@ -155,25 +152,28 @@ angular.module('cesium.map.wot.controllers', ['cesium.services', 'cesium.map.ser
};
// Load markers data
$scope.load = function() {
$scope.load = function(map) {
if (!map) {
return leafletData.getMap($scope.mapId).then(function(map) {
return $scope.load(map); // loop with the map object
});
}
$scope.loading = true;
// Show loading indicator
map.fire('dataloading');
// Load wot data
return mapWot.load()
.then(function(res) {
var markers = {};
var existingMarkerIds = _.keys($scope.map.markers);
if (res && res.length) {
var formatPubkey = $filter('formatPubkey');
var markerTemplate = $templateCache.get('plugins/map/templates/wot/popup_marker.html');
// Sort with member first
/*res = _.sortBy(res, function(hit) {
var score = 0;
score += (!hit.uid) ? 100 : 0;
return -score;
});*/
_.forEach(res, function (hit) {
var type = hit.pending ? 'pending' : (hit.uid ? 'member' : 'wallet');
var shortPubkey = formatPubkey(hit.pubkey);
......@@ -193,11 +193,10 @@ angular.module('cesium.map.wot.controllers', ['cesium.services', 'cesium.map.ser
message: markerTemplate
};
var id = (hit.uid ? (hit.uid + ':' + hit.pubkey) : hit.pubkey).replace(/-/g, '_');
var wasExisting = !!$scope.map.markers[id];
$scope.map.markers[id] = marker;
markers[id] = marker;
// Create a search marker (will be hide)
if (!wasExisting) {
if (!existingMarkerIds[id]) {
var searchText = hit.name + ((hit.uid && hit.uid != hit.name) ? (' | ' + hit.uid) : '') + ' | ' + shortPubkey;
var searchMarker = angular.merge({
type: type,
......@@ -215,8 +214,11 @@ angular.module('cesium.map.wot.controllers', ['cesium.services', 'cesium.map.ser
}
});
}
$scope.map.markers = markers;
$scope.loading = false;
// hide loading indicator
map.fire('dataload');
});
};
......
......@@ -6,7 +6,8 @@ angular.module('cesium.map.plugin', [
// Controllers
'cesium.map.wot.controllers',
'cesium.map.network.controllers',
'cesium.map.user.controllers'
'cesium.map.user.controllers',
'cesium.map.settings.controllers'
])
// Configure plugin
......
......@@ -26,7 +26,7 @@ angular.module('cesium.map.wot.services', ['cesium.services'])
};
// Limit to profile with geo point
if (esGeo.google.hasApiKey()) {
if (options.searchAddress) {
query.bool.should = [
{exists: {field: "geoPoint"}},
{exists: {field: "address"}},
......@@ -63,6 +63,7 @@ angular.module('cesium.map.wot.services', ['cesium.services'])
options = options || {};
options.from = options.from || 0;
options.size = options.size || constants.DEFAULT_LOAD_SIZE;
options.searchAddress = esGeo.google.isEnable() && (angular.isDefined(options.searchAddress) ? options.searchAddress : true);
var request = {
query: createFilterQuery(options),
......@@ -107,7 +108,7 @@ angular.module('cesium.map.wot.services', ['cesium.services'])
// Transform profile hits
var commaRegexp = new RegExp('[,]');
var noPositionItems = [];
var searchAddressItems = [];
var items = res.hits.hits.reduce(function(res, hit) {
var pubkey = hit._id;
......@@ -117,13 +118,13 @@ angular.module('cesium.map.wot.services', ['cesium.services'])
// City & address
item.city = hit._source.city;
item.fullAddress = item.city && ((hit._source.address ? hit._source.address+ ', ' : '') + item.city);
// Set geo point
item.geoPoint = hit._source.geoPoint;
if (!item.geoPoint || !item.geoPoint.lat || !item.geoPoint.lon) {
if (!item.fullAddress) return res; // no city: exclude this item
noPositionItems.push(item);
if (!options.searchAddress || !item.city) return res; // no city: exclude this item
item.searchAddress = item.city && ((hit._source.address ? hit._source.address+ ', ' : '') + item.city);
searchAddressItems.push(item);
}
else {
// Convert lat/lon to float (if need)
......@@ -152,24 +153,29 @@ angular.module('cesium.map.wot.services', ['cesium.services'])
}, []);
// Resolve missing positions by addresses (only if google API enable)
if (noPositionItems.length && esGeo.google.hasApiKey()) {
if (searchAddressItems.length) {
var now = new Date().getTime();
console.log('[map] [wot] Search positions of {0} addresses...'.format(noPositionItems.length));
console.log('[map] [wot] Search positions of {0} addresses...'.format(searchAddressItems.length));
var counter = 0;
return $q.all(noPositionItems.reduce(function(res, item) {
return res.concat(esGeo.google.searchByAddress(item.fullAddress)
return $q.all(searchAddressItems.reduce(function(res, item) {
return !item.searchAddress ? res : res.concat(esGeo.google.searchByAddress(item.searchAddress)
.then(function(res) {
if (!res || !res.length) return;
item.geoPoint = res[0];
delete item.fullAddress;
// If search on city, add a randomized delta to avoid superposition
if (item.city == item.searchAddress) {
item.geoPoint.lon += Math.random() / 1000;
item.geoPoint.lat += Math.random() / 1000;
}
delete item.searchAddress; // not need anymore
items.push(item);
counter++;
})
.catch(function() {/*silent*/}));
}, []))
.then(function(){
console.log('[map] [wot] Resolved {0}/{1} addresses in {2}ms'.format(counter, noPositionItems.length, new Date().getTime()-now));
console.log('[map] [wot] Resolved {0}/{1} addresses in {2}ms'.format(counter, searchAddressItems.length, new Date().getTime()-now));
return items;
});
}
......
<ion-view left-buttons="leftButtons" class="view-map-network">
<ion-nav-title>
<span class="hidden-xs" translate>MAP.NETWORK.VIEW.TITLE</span>
</ion-nav-title>
<ion-nav-buttons side="secondary">
......
<span class="item item-divider" translate>MAP.SETTINGS.MAP_DIVIDER</span>
<label class="item item-toggle dark item-text-wrap">
<span translate>MAP.SETTINGS.ENABLE_GOOGLE_API</span>
<h4 class="gray" translate>MAP.SETTINGS.ENABLE_GOOGLE_API_HELP</h4>
<div class="toggle toggle-royal">
<input type="checkbox" ng-model="formData.enableGoogleApi">
<div class="track">
<div class="handle"></div>
</div>
</div>
</label>
<ion-item class="item-input"
ng-if="!$root.config.plugins.es.googleApiKey"
ng-class="{'item-input-error': formData.enableGoogleApi && !formData.googleApiKey}">
<div class="input-label col-33">
<span class="" ng-class="{'gray': !formData.enableGoogleApi}"translate>MAP.SETTINGS.GOOGLE_API_KEY</span>
<h4>
<a href="https://console.developers.google.com/apis/credentials/key" target="_system" translate>MAP.SETTINGS.BTN_GOOGLE_API</a>
<span class="gray"> ({{'MAP.SETTINGS.BTN_GOOGLE_API_WARNING'|translate}})</span>
</h4>
</div>
<input type="text"
placeholder="{{'MAP.SETTINGS.GOOGLE_API_KEY_PLACEHOLDER' | translate}}"
ng-model="formData.googleApiKey"
ng-if="formData.enableGoogleApi">
</ion-item>
<div class="form-errors"
ng-show="formData.enableGoogleApi && !formData.googleApiKey">
<div class="form-error">
<span translate="ERROR.FIELD_REQUIRED"></span>
</div>
</div>
......@@ -18,6 +18,6 @@
</h4>
</div>
</div>
<div class="item no-border no-padding item-text-wrap" ng-if="::hit.description">
<div class="item no-border no-padding item-text-wrap hidden-xs" ng-if="::hit.description">
<small ng-bind-html="::hit.description"></small>
</div>
<ion-view left-buttons="leftButtons" class="view-map-wot">
<ion-nav-title>
<span class="hidden-xs" translate>MAP.WOT.VIEW.TITLE</span>
</ion-nav-title>
<ion-nav-buttons side="secondary">
<button class="button button-icon button-clear icon ion-loop visible-xs visible-sm" ng-click="load()">
</button>
</ion-nav-buttons>
<ion-content data-tap-disabled="true">
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment