diff options
| -rw-r--r-- | ishtar_common/static/js/ishtar-map.js | 237 | ||||
| -rw-r--r-- | ishtar_common/static/js/ishtar.js | 1 | ||||
| -rw-r--r-- | ishtar_common/views_item.py | 5 | ||||
| -rw-r--r-- | scss/custom.scss | 26 | 
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;  } | 
