summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorÉtienne Loks <etienne.loks@iggdrasil.net>2019-02-04 19:06:55 +0100
committerÉtienne Loks <etienne.loks@iggdrasil.net>2019-04-24 19:38:56 +0200
commit8aa62f20d06252249abb87a84ac265a40016c712 (patch)
tree2293abaeaae428986477c12c6686b26d0b66af76
parent9f1286f37a08cd6d02934c923ae3f170ed4d3a05 (diff)
downloadIshtar-8aa62f20d06252249abb87a84ac265a40016c712.tar.bz2
Ishtar-8aa62f20d06252249abb87a84ac265a40016c712.zip
Map JS: display popup - manage click on cluster - hover
-rw-r--r--ishtar_common/static/js/ishtar-map.js237
-rw-r--r--ishtar_common/static/js/ishtar.js1
-rw-r--r--ishtar_common/views_item.py5
-rw-r--r--scss/custom.scss26
4 files changed, 250 insertions, 19 deletions
diff --git a/ishtar_common/static/js/ishtar-map.js b/ishtar_common/static/js/ishtar-map.js
index 3cce09fcb..1d84adb49 100644
--- a/ishtar_common/static/js/ishtar-map.js
+++ b/ishtar_common/static/js/ishtar-map.js
@@ -1,13 +1,16 @@
-/* layers */
-
+/* default variables */
var default_pointer = "/media/images/default-pointer.png";
var marker_cluster = "/media/images/marker-cluster.png";
var view_projection = 'EPSG:3857';
+var animation_duration = 250;
var map_default_center = 'SRID=4326;POINT (2.4397 46.5528)';
var map_default_zoom = '7';
+var min_auto_zoom_on_cluster = 13;
+
+/* base layers */
var source_osm = function(options){
return new ol.layer.Tile({
@@ -15,7 +18,6 @@ var source_osm = function(options){
});
};
-
var default_map_layers = {
'osm': source_osm
};
@@ -83,10 +85,11 @@ var enable_clustering = function(){
function cluster_get_style (feature, resolution){
feature.set('key', 'cluster');
- var features = feature.get('features');
+ var cluster_features = feature.get('features');
- var size = features.length;
+ var size = cluster_features.length;
feature.set('size', size);
+
var style = _styleCache[size];
// no cluster for lonely marker
@@ -100,7 +103,7 @@ var enable_clustering = function(){
radius: radius,
stroke: new ol.style.Stroke({
color:"rgba("+color+",0.5)",
- width:15
+ width: 15
}),
fill: new ol.style.Fill({
color:"rgba("+color+",1)"
@@ -122,8 +125,8 @@ var enable_clustering = function(){
}
if (size > 1){
// marker himself disappear
- for (idx in features){
- var feat = features[idx];
+ for (idx in cluster_features){
+ var feat = cluster_features[idx];
if (!_remindUpdated[feat.getProperties()['id']]){
if (!_remindOldStyle[feat.getProperties()['id']]){
_remindOldStyle[feat.getProperties()['id']] = feat.getStyle();
@@ -134,7 +137,7 @@ var enable_clustering = function(){
}
} else {
// or re-appear
- var feat = features[0];
+ var feat = cluster_features[0];
if (!_remindUpdated[feat.getProperties()['id']] &&
_remindOldStyle[feat.getProperties()['id']]){
feat.setStyle(_remindOldStyle[feat.getProperties()['id']]);
@@ -167,6 +170,200 @@ var reinit_clustering = function(){
_currentRemind = -1;
};
+/* manage clicks */
+
+var current_feature;
+var animate_in_progress = false;
+
+var animate_end = function(){animate_in_progress = false};
+
+var wait_animation_end = function(callback, retry){
+ if (!retry) retry = 1;
+ setTimeout(function(){
+ retry += 1
+ if (retry < 5 && animate_in_progress){
+ wait_animation_end(callback)
+ } else {
+ callback();
+ }
+ }, 100);
+};
+
+var manage_click_on_map = function(e) {
+ var feature = map.forEachFeatureAtPixel(
+ e.pixel,
+ function(feature, layer) {
+ return feature;
+ }
+ );
+ click_on_feature(feature);
+};
+
+var click_on_feature = function(feature){
+
+ if (typeof feature == 'undefined'){
+ current_feature = null;
+ return;
+ }
+ if (current_feature == feature){
+ return
+ }
+ current_feature = feature;
+ if (!feature) return;
+ $(popup_item).hide();
+
+ var timeout = 200;
+ setTimeout(function(){
+ // zoom on aggregated
+ var key = feature.get('key');
+ if (key && key.length > 6 && key.substring(0, 7) == 'cluster'){
+ feature = click_on_cluster(feature);
+ } else {
+ feature = click_on_pointer(feature);
+ }
+ }, timeout);
+};
+
+var click_on_cluster = function(feature, zoom_level, duration, nb_zoom,
+ current_nb_items){
+ if (!duration){
+ // zoom animation must be slower
+ duration = animation_duration * 2;
+ }
+ if (!nb_zoom) nb_zoom = 0;
+
+ var props = feature.getProperties();
+ if (!'features' in props) return feature;
+ if (!current_nb_items){
+ current_nb_items = props['features'].length;
+ } else if(current_nb_items != props['features'].length) {
+ // stop zooming there less item in the cluster
+ return feature;
+ }
+
+ var v = map.getView();
+ if (!zoom_level) zoom_level = v.getZoom() + 1;
+
+ // center
+ var new_center = feature.getGeometry().getCoordinates()
+ // max zoom reached
+ if (zoom_level >= min_auto_zoom_on_cluster){
+ animate_in_progress = true;
+ v.animate({center: new_center, duration: duration}, animate_end);
+ return display_cluster_detail(feature);
+ }
+
+ // zoom
+ animate_in_progress = true;
+ v.animate({center: new_center, zoom: zoom_level, duration: duration},
+ animate_end);
+
+ nb_zoom += 1;
+ // something wrong stop zoom!
+ if (nb_zoom > v.getMaxZoom()) return feature;
+
+ // wait for the animation to finish before rezoom
+ return setTimeout(
+ function(){
+ // our cluster must be at the center (if it exists after zoom)
+ var pixel = map.getPixelFromCoordinate(v.getCenter());
+ var new_feature;
+ map.forEachFeatureAtPixel(
+ pixel, function(feat, layer){
+ if (layer == cluster_layer){
+ new_feature = feat;
+ return true
+ }
+ }
+ );
+ if (new_feature){
+ if (zoom_level < min_auto_zoom_on_cluster){
+ return display_cluster_detail(
+ new_feature, zoom_level + 1, duration, nb_zoom,
+ current_nb_items);
+ }
+ return click_on_cluster(
+ new_feature, zoom_level + 1, duration, nb_zoom,
+ current_nb_items);
+ }
+ // no more cluster feature here or min auto zoom reach: stop zooming
+ return feature;
+ }, duration + 200);
+};
+
+var click_on_pointer = function(feature){
+ console.log("click_on_pointer");
+ var features = new Array();
+ features.push(feature)
+ display_items(features, -14, -54);
+};
+
+/* display info */
+
+var display_cluster_detail = function(cluster){
+ console.log("display_cluster_detail");
+ display_items(cluster.getProperties()['features'], -14, -21);
+};
+
+var display_items = function(features, offset_x, offset_y){
+ wait_animation_end(function() {_display_items(features, offset_x, offset_y)});
+};
+
+var _display_items = function(features, offset_x, offset_y){
+ console.log("display_items");
+ var feature = features[0];
+ var geom = feature.getGeometry();
+ var popup_content = "<ul>";
+ features.forEach(function(feat){
+ var properties = feat.getProperties();
+ popup_content += "<li>" + properties['link'] + " " + properties['name'] + "</li>"
+ console.log(properties);
+ });
+ popup_content += "</ul>";
+ $(popup_item).html(popup_content);
+ popup.setPosition([0, 0]); // out of the map
+ $(popup_item).show(function(){
+
+ offset_x -= $(popup_item).width() / 2;
+ console.log(offset_x);
+ popup.setOffset([offset_x, offset_y]);
+ popup.setPosition(geom.getCoordinates());
+ });
+};
+
+/* hover */
+
+var manage_hover = function(e) {
+ var pixel = map.getEventPixel(e.originalEvent);
+ var feature = map.forEachFeatureAtPixel(
+ e.pixel,
+ function(feature, layer) {
+ return feature;
+ }
+ );
+ var hit = map.hasFeatureAtPixel(pixel);
+ var target = map.getTarget();
+ target = typeof target === "string" ?
+ document.getElementById(target) : target;
+ target.style.cursor = hit ? 'pointer' : '';
+};
+
+/* popup */
+
+var popup;
+var popup_item;
+
+var init_popup = function(){
+ popup_item = document.getElementById("ishtar-map-popup-" + map_id);
+ var popup_options = {
+ element: popup_item,
+ positioning: 'bottom-left',
+ stopEvent: false
+ }
+ popup = new ol.Overlay(popup_options);
+ map.addOverlay(popup);
+};
+
/* display map */
var vector_source;
@@ -174,6 +371,7 @@ var vector_layer;
var center;
var point_features;
var map;
+var map_id;
var map_view;
var map_layers;
var proj_options = {
@@ -182,7 +380,7 @@ var proj_options = {
var geojson_format = new ol.format.GeoJSON(proj_options);
var wkt_format = new ol.format.WKT(proj_options);
-var initialize_map = function(map_id, layers){
+var initialize_map = function(layers){
center = wkt_format.readGeometry(map_default_center).getCoordinates();
/*
@@ -213,18 +411,21 @@ var initialize_map = function(map_id, layers){
}
-var redraw_map = function(map_id, layers){
+var redraw_map = function(layers){
map.setTarget(null);
map = null;
- initialize_map(map_id, layers);
+ initialize_map(layers);
+ reinit_clustering();
+ current_feature = null;
};
-var display_map = function(map_id, points, layers){
+var display_map = function(current_map_id, points, layers){
+ map_id = current_map_id;
if (map){
- redraw_map(map_id, layers);
+ redraw_map(layers);
} else {
- initialize_map(map_id, layers);
+ initialize_map(layers);
}
point_features = geojson_format.readFeatures(points);
vector_source.clear();
@@ -240,5 +441,9 @@ var display_map = function(map_id, points, layers){
enable_clustering();
cluster_source.getSource().addFeatures(point_features);
-}
+ init_popup();
+
+ map.on('click', manage_click_on_map);
+ map.on('pointermove', manage_hover);
+}
diff --git a/ishtar_common/static/js/ishtar.js b/ishtar_common/static/js/ishtar.js
index 96f1a4cc9..929629397 100644
--- a/ishtar_common/static/js/ishtar.js
+++ b/ishtar_common/static/js/ishtar.js
@@ -1241,6 +1241,7 @@ var render_map = function(data_table, table_name, nb_select, map_id){
//var html = render_paginate_select(table_name, 'map', nb_select);
var html = "";
html += "<div class='ishtar-table-map' id='" + map_id + "'></div>";
+ html += "<div class='ishtar-map-popup' id='ishtar-map-popup-" + map_id + "'></div>";
return {"points": data_table, "html": html};
};
diff --git a/ishtar_common/views_item.py b/ishtar_common/views_item.py
index 72381ed8b..275414d0e 100644
--- a/ishtar_common/views_item.py
+++ b/ishtar_common/views_item.py
@@ -1018,7 +1018,7 @@ def _format_geojson(rows):
if not geo_attr or not name_attr:
return data
for row in rows:
- feat = {'name': row[name_attr], 'id': row['id']}
+ feat = {'name': row[name_attr], 'id': row['id'], 'link': row['link']}
if not row.get(geo_attr, None):
data['no-geo'].append(feat)
continue
@@ -1624,8 +1624,7 @@ def get_item(model, func_name, default_name, extra_request_keys=None,
+ unicode(data[0]) + ") url not available")
lnk = ''
res = {'id': data[0]}
- if data_type != 'json-map':
- res['link'] = lnk
+ res['link'] = lnk
for idx, value in enumerate(data[1:]):
if value:
table_col = table_cols[idx]
diff --git a/scss/custom.scss b/scss/custom.scss
index 0420ad8e1..b5b97baaf 100644
--- a/scss/custom.scss
+++ b/scss/custom.scss
@@ -673,6 +673,32 @@ ul.compact{
height: 50%;
}
+.ishtar-map-popup{
+ background-color: #fff;
+ padding: 0.5em 1em;
+ border-radius: 0.5em;
+ border-style: solid;
+ border-width: 1px;
+ border-color: $gray-500;
+}
+
+.ishtar-map-popup ul {
+ margin: 0;
+ padding: 0;
+ list-style: none;
+}
+
+.ishtar-map-popup::after {
+ content: "";
+ position: absolute;
+ top: 100%;
+ left: 50%;
+ margin-left: -5px;
+ border-width: 5px;
+ border-style: solid;
+ border-color: $gray-700 transparent transparent transparent;
+}
+
.lg .lg-sub-html{
text-align: left;
}