]> git.openstreetmap.org Git - rails.git/commitdiff
Initial work on overpass based query API
authorTom Hughes <tom@compton.nu>
Sat, 8 Mar 2014 17:40:27 +0000 (17:40 +0000)
committerTom Hughes <tom@compton.nu>
Sun, 16 Mar 2014 16:18:12 +0000 (16:18 +0000)
app/assets/images/sprite.png
app/assets/images/sprite.svg
app/assets/javascripts/index.js
app/assets/javascripts/index/query.js [new file with mode: 0644]
app/assets/javascripts/leaflet.query.js [new file with mode: 0644]
app/assets/stylesheets/common.css.scss
app/views/browse/query.html.erb [new file with mode: 0644]
config/i18n-js.yml
config/locales/en.yml
config/routes.rb

index e7490c84cbca1a7852d89a3011b3c1f23d541e3f..e3ed0e7f81185d93ae6d75b8aab27d20d521242a 100644 (file)
Binary files a/app/assets/images/sprite.png and b/app/assets/images/sprite.png differ
index b61018d135276d031f0fb64a7e1611ac03305ecb..b50b969e909620ce71400c28d5c54c4dc409e1f4 100644 (file)
@@ -13,8 +13,8 @@
    height="200"
    id="svg2"
    version="1.1"
-   inkscape:version="0.48.2 r9819"
-   inkscape:export-filename="/Users/tmcw/src/openstreetmap-website/app/assets/images/sprite.png"
+   inkscape:version="0.48.4 r9939"
+   inkscape:export-filename="/home/tom/rails/app/assets/images/sprite.png"
    inkscape:export-xdpi="90"
    inkscape:export-ydpi="90"
    sodipodi:docname="sprite.svg">
      borderopacity="1.0"
      inkscape:pageopacity="0.0"
      inkscape:pageshadow="2"
-     inkscape:zoom="4"
-     inkscape:cx="210.42032"
-     inkscape:cy="175.54808"
+     inkscape:zoom="16"
+     inkscape:cx="258.2457"
+     inkscape:cy="193.60262"
      inkscape:document-units="px"
      inkscape:current-layer="layer1"
      showgrid="false"
-     inkscape:window-width="1436"
-     inkscape:window-height="856"
-     inkscape:window-x="4"
-     inkscape:window-y="0"
+     inkscape:window-width="1366"
+     inkscape:window-height="702"
+     inkscape:window-x="0"
+     inkscape:window-y="27"
      inkscape:window-maximized="1"
      showguides="true"
      inkscape:guide-bbox="true"
        orientation="1,0"
        position="260,195"
        id="guide11761" />
+    <sodipodi:guide
+       orientation="1,0"
+       position="280,153.875"
+       id="guide3019" />
   </sodipodi:namedview>
   <metadata
      id="metadata7">
        inkscape:export-filename="/Users/saman/work_repos/osm-redesign/renders/share-1.png"
        inkscape:export-xdpi="90"
        inkscape:export-ydpi="90" />
+    <text
+       xml:space="preserve"
+       style="font-size:20px;font-style:normal;font-weight:bold;line-height:125%;letter-spacing:0px;word-spacing:0px;fill:#ffffff;fill-opacity:1;stroke:none;font-family:Sans;-inkscape-font-specification:Sans Bold"
+       x="264.8125"
+       y="869.62622"
+       id="text3021"
+       sodipodi:linespacing="125%"><tspan
+         sodipodi:role="line"
+         id="tspan3023"
+         x="264.8125"
+         y="869.62622">?</tspan></text>
   </g>
 </svg>
index dc3c932773066ce56a518a9b1c921659cf5ee7e8..ddec2bffe9824d143fd2994e1d93de9032a56c72 100644 (file)
@@ -5,6 +5,7 @@
 //= require leaflet.key
 //= require leaflet.note
 //= require leaflet.share
+//= require leaflet.query
 //= require index/search
 //= require index/browse
 //= require index/export
@@ -12,6 +13,7 @@
 //= require index/history
 //= require index/note
 //= require index/new_note
+//= require index/query
 //= require router
 
 (function() {
@@ -123,6 +125,11 @@ $(document).ready(function () {
     sidebar: sidebar
   }).addTo(map);
 
+  L.OSM.query({
+    position: position,
+    sidebar: sidebar
+  }).addTo(map);
+
   L.control.scale()
     .addTo(map);
 
@@ -294,7 +301,8 @@ $(document).ready(function () {
     "/node/:id(/history)":         OSM.Browse(map, 'node'),
     "/way/:id(/history)":          OSM.Browse(map, 'way'),
     "/relation/:id(/history)":     OSM.Browse(map, 'relation'),
-    "/changeset/:id":              OSM.Browse(map, 'changeset')
+    "/changeset/:id":              OSM.Browse(map, 'changeset'),
+    "/query":                      OSM.Query(map)
   });
 
   if (OSM.preferred_editor == "remote" && document.location.pathname == "/edit") {
diff --git a/app/assets/javascripts/index/query.js b/app/assets/javascripts/index/query.js
new file mode 100644 (file)
index 0000000..8d04efd
--- /dev/null
@@ -0,0 +1,244 @@
+OSM.Query = function(map) {
+  var queryButton = $(".control-query .control-button"),
+    uninterestingTags = ['source', 'source_ref', 'source:ref', 'history', 'attribution', 'created_by', 'tiger:county', 'tiger:tlid', 'tiger:upload_uuid'],
+    marker;
+
+  queryButton.on("click", function (e) {
+    e.preventDefault();
+    e.stopPropagation();
+
+    if (queryButton.hasClass("active")) {
+      disableQueryMode();
+
+      OSM.router.route("/");
+    } else {
+      enableQueryMode();
+    }
+  });
+
+  $("#sidebar_content")
+    .on("mouseover", ".query-results li", function () {
+      var geometry = $(this).data("geometry")
+      if (geometry) map.addLayer(geometry);
+      $(this).addClass("selected");
+    })
+    .on("mouseout", ".query-results li", function () {
+      var geometry = $(this).data("geometry")
+      if (geometry) map.removeLayer(geometry);
+      $(this).removeClass("selected");
+    });
+
+  function interestingFeature(feature) {
+    if (feature.tags) {
+      for (var key in feature.tags) {
+        if (uninterestingTags.indexOf(key) < 0) {
+          return true;
+        }
+      }
+    }
+
+    return false;
+  }
+
+  function featurePrefix(feature) {
+    var tags = feature.tags;
+    var prefix = "";
+
+    if (tags.boundary === "administrative") {
+      prefix = I18n.t("geocoder.search_osm_nominatim.admin_levels.level" + tags.admin_level)
+    } else {
+      var prefixes = I18n.t("geocoder.search_osm_nominatim.prefix");
+
+      for (var key in tags) {
+        var value = tags[key];
+
+        if (prefixes[key]) {
+          if (prefixes[key][value]) {
+            return prefixes[key][value];
+          } else {
+            var first = value.substr(0, 1).toUpperCase(),
+              rest = value.substr(1).replace(/_/g, " ");
+
+            return first + rest;
+          }
+        }
+      }
+    }
+
+    if (!prefix) {
+      prefix = I18n.t("javascripts.query." + feature.type);
+    }
+
+    return prefix;
+  }
+
+  function featureName(feature) {
+    var tags = feature.tags;
+
+    if (tags["name"]) {
+      return tags["name"];
+    } else if (tags["ref"]) {
+      return tags["ref"];
+    } else if (tags["addr:housenumber"] && tags["addr:street"]) {
+      return tags["addr:housenumber"] + " " + tags["addr:street"];
+    } else {
+      return "#" + feature.id;
+    }
+  }
+
+  function featureLink(feature) {
+    if (feature.type === "area") {
+      if (feature.id >= 3600000000) {
+        var id = feature.id - 3600000000;
+
+        return "/browse/relation/" + id;
+      } else if (feature.id >= 2400000000) {
+        var id = feature.id - 2400000000;
+
+        return "/browse/way/" + id;
+      } else {
+        return "/browse/node/" + feature.id;
+      }
+    } else {
+      return "/browse/" + feature.type + "/" + feature.id;
+    }
+  }
+
+  function featureGeometry(feature, nodes) {
+    var geometry;
+
+    if (feature.type === "node") {
+      geometry = L.circleMarker([feature.lat, feature.lon]);
+    } else if (feature.type === "way") {
+      geometry = L.polyline(feature.nodes.map(function (node) {
+        return nodes[node];
+      }));
+    }
+
+    return geometry;
+  }
+
+  function runQuery(query, $section) {
+    var $ul = $section.find("ul");
+
+    $ul.empty();
+    $section.show();
+
+    $section.find(".loader").oneTime(1000, "loading", function () {
+      $(this).show();
+    });
+
+    $.ajax({
+      url: "http://overpass-api.de/api/interpreter",
+      method: "GET",
+      data: {
+        data: "[timeout:5][out:json];" + query,
+      },
+      success: function(results) {
+        var nodes = {};
+
+        $section.find(".loader").stopTime("loading").hide();
+
+        results.elements.forEach(function (element) {
+          if (element.type === "node") {
+            nodes[element.id] = [element.lat, element.lon];
+          }
+        });
+
+        for (var i = 0; i < results.elements.length; i++) {
+          var element = results.elements[i];
+
+          if (interestingFeature(element)) {
+            var $li = $("<li>")
+              .data("geometry", featureGeometry(element, nodes))
+              .appendTo($ul);
+            var $p = $("<p>")
+              .addClass("inner12 search_results_entry clearfix")
+              .text(featurePrefix(element) + " ")
+              .appendTo($li);
+
+            $("<a>")
+              .attr("href", featureLink(element))
+              .text(featureName(element))
+              .appendTo($p);
+          }
+        }
+      }
+    });
+  }
+
+  function queryOverpass(lat, lng) {
+    var latlng = L.latLng(lat, lng),
+      around = "around:10.0," + lat + "," + lng,
+      features = "(node(" + around + ");way(" + around + ");relation(" + around + "))",
+      nearby = "((" + features + ";way(bn));node(w));out;",
+      isin = "(is_in(" + lat + "," + lng + ");>);out;";
+
+    $("#sidebar_content .query-intro")
+      .hide();
+
+    if (marker) {
+      marker.setLatLng(latlng).addTo(map);
+    } else {
+      marker = L.circle(latlng, 10, { clickable: false }).addTo(map);
+    }
+
+    $(document).everyTime(75, "fadeQueryMarker", function (i) {
+      if (i == 10) {
+        map.removeLayer(marker);
+      } else {
+        marker.setStyle({
+          opacity: 0.5 - i * 0.05,
+          fillOpacity: 0.2 - i * 0.02
+        });
+      }
+    }, 10);
+
+    runQuery(nearby, $("#query-nearby"));
+    runQuery(isin, $("#query-isin"));
+  }
+
+  function clickHandler(e) {
+    var precision = OSM.zoomPrecision(map.getZoom()),
+      lat = e.latlng.lat.toFixed(precision),
+      lng = e.latlng.lng.toFixed(precision);
+
+    OSM.router.route("/query?lat=" + lat + "&lon=" + lng);
+  }
+
+  function enableQueryMode() {
+    queryButton.addClass("active");
+    map.on("click", clickHandler);
+    $(map.getContainer()).addClass("query-active");
+  }
+
+  function disableQueryMode() {
+    if (marker) map.removeLayer(marker);
+    $(map.getContainer()).removeClass("query-active");
+    map.off("click", clickHandler);
+    queryButton.removeClass("active");
+  }
+
+  var page = {};
+
+  page.pushstate = page.popstate = function(path) {
+    OSM.loadSidebarContent(path, function () {
+      page.load(path);
+    });
+  };
+
+  page.load = function(path) {
+    var params = querystring.parse(path.substring(path.indexOf('?') + 1));
+
+    queryOverpass(params.lat, params.lon);
+    enableQueryMode();
+
+    return map.getState();
+  };
+
+  page.unload = function() {
+    disableQueryMode();
+  };
+
+  return page;
+};
diff --git a/app/assets/javascripts/leaflet.query.js b/app/assets/javascripts/leaflet.query.js
new file mode 100644 (file)
index 0000000..3eab905
--- /dev/null
@@ -0,0 +1,19 @@
+L.OSM.query = function (options) {
+  var control = L.control(options);
+
+  control.onAdd = function (map) {
+    var $container = $('<div>')
+      .attr('class', 'control-query');
+
+    var link = $('<a>')
+      .attr('class', 'control-button')
+      .attr('href', '#')
+      .attr('data-original-title', I18n.t('javascripts.site.queryfeature_tooltip'))
+      .html('<span class="icon query"></span>')
+      .appendTo($container);
+
+    return $container[0];
+  };
+
+  return control;
+};
index 533e91c69ec4d681d276efb34bba8e8f2184346b..c088e9429d6e6cb90f87f7fe8c1aa11ebb786dd4 100644 (file)
@@ -171,7 +171,7 @@ small, aside {
 .icon.close:hover { background-position: -200px -20px; }
 .icon.check       { background-position: -220px 0; }
 .icon.note        { background-position: -240px 0; }
-.icon.gear        { background-position: -260px 0; }
+.icon.query       { background-position: -260px 0; }
 
 /* Rules for links */
 
@@ -683,6 +683,10 @@ nav.secondary {
   #map {
     height: 100%;
     overflow: hidden;
+
+    &.query-active {
+      cursor: help;
+    }
   }
 
   #map-ui {
@@ -1119,6 +1123,14 @@ header .search_form {
     overflow: hidden;
     margin: 0 0 10px 10px;
   }
+
+  .query-results {
+    display: none;
+
+    ul.results-list li.selected {
+      background: #FFFFE6;
+    }
+  }
 }
 
 /* Rules for export sidebar */
diff --git a/app/views/browse/query.html.erb b/app/views/browse/query.html.erb
new file mode 100644 (file)
index 0000000..62ab94e
--- /dev/null
@@ -0,0 +1,22 @@
+<% set_title(t "browse.query.title") %>
+
+<h2>
+  <a class="geolink" href="<%= root_path %>"><span class="icon close"></span></a>
+  <%= t "browse.query.title" %>
+</h2>
+
+<div class="browse-section query-intro">
+  <p><%= t("browse.query.introduction") %></p>
+</div>
+
+<div id="query-nearby" class="browse-section query-results">
+  <h4><%= t("browse.query.nearby") %></h4>
+  <%= image_tag "searching.gif", :class => "loader" %>
+  <ul class="results-list"></ul>
+</div>
+
+<div id="query-isin" class="browse-section query-results">
+  <h4><%= t("browse.query.enclosing") %></h4>
+  <%= image_tag "searching.gif", :class => "loader" %>
+  <ul class="results-list"></ul>
+</div>
index 026ece64c11053f181dc1220921a187c683e5927..369fa340a2cd6611c538e9e93706ed0a7ea40154 100644 (file)
@@ -31,3 +31,4 @@ translations:
     - "*.site.sidebar.search_results"
     - "*.diary_entry.edit.marker_text"
     - "*.layouts.project_name.title"
+    - "*.geocoder.search_osm_nominatim.*"
index 94537c1fb40d0ce1654185e32fe613ca2ac70a02..4db668b38d3542daa0df25c9438226eb7e74945b 100644 (file)
@@ -197,6 +197,11 @@ en:
       reopened_by: "Reactivated by %{user} <abbr title='%{exact_time}'>%{when} ago</abbr>"
       reopened_by_anonymous: "Reactivated by anonymous <abbr title='%{exact_time}'>%{when} ago</abbr>"
       hidden_by: "Hidden by %{user} <abbr title='%{exact_time}'>%{when} ago</abbr>"
+    query:
+      title: "Query Features"
+      introduction: "Click on the map to find nearby features."
+      nearby: "Nearby features"
+      enclosing: "Enclosing features"
   changeset:
     changeset_paging_nav:
       showing_page: "Page %{page}"
@@ -507,7 +512,7 @@ en:
           primary_link: "Primary Road"
           proposed: "Proposed Road"
           raceway: "Raceway"
-          residential: "Residential"
+          residential: "Residential Road"
           rest_area: "Rest Area"
           road: "Road"
           secondary: "Secondary Road"
@@ -718,6 +723,8 @@ en:
           tram: "Tramway"
           tram_stop: "Tram Stop"
           yard: "Railway Yard"
+        route:
+          bus: "Bus Route"
         shop:
           alcohol: "Off License"
           antiques: "Antiques"
@@ -2101,6 +2108,7 @@ en:
       createnote_disabled_tooltip: Zoom in to add a note to the map
       map_notes_zoom_in_tooltip: Zoom in to see map notes
       map_data_zoom_in_tooltip: Zoom in to see map data
+      queryfeature_tooltip: Query features
     notes:
       new:
         intro: "Spotted a mistake or something missing? Let other mappers know so we can fix it. Move the marker to the correct position and type a note to explain the problem. (Please don't enter personal information here.)"
@@ -2113,6 +2121,10 @@ en:
         comment_and_resolve: Comment & Resolve
         comment: Comment
     edit_help: Move the map and zoom in on a location you want to edit, then click here.
+    query:
+      node: Node
+      way: Way
+      relation: Relation
   redaction:
     edit:
       description: "Description"
index e9f593d92bdc4b4d53da185f2bfc0ceff03bf4bf..e03c5d632110ba225e340a48feb5a4a00c490c67 100644 (file)
@@ -150,6 +150,7 @@ OpenStreetMap::Application.routes.draw do
   match '/offline' => 'site#offline', :via => :get
   match '/key' => 'site#key', :via => :get
   match '/id' => 'site#id', :via => :get
+  match '/query' => 'browse#query', :via => :get
   match '/user/new' => 'user#new', :via => :get
   match '/user/new' => 'user#create', :via => :post
   match '/user/terms' => 'user#terms', :via => :get