Adding geocoding with autocompletion

In this tutorial we will show you how to use the Route360° geocoding service to let users enter an address, which we will use for the polygons source. We will use an angular md-autocomplete directive and bind an action to add or move the marker as well as clearing already shown polygons.

We will also show you how to update the input field in case the user drags a marker on the map. This can be done through reverse geocoding, which the Route360° API makes really easy.

To not lose track which autocomplete belongs to which marker on the map, we also show how you can use a marker image to visualize the connection. This service is based on Photon, an open source geocoder built for OpenStreetMap data - check it out and use it/contribute to it.

Adding geocoding with autocompletion

Create polygons from user-defined address

GET YOUR FREE API KEY to use this example
reload
open in new tab
copy HTML
hide code
<!DOCTYPE html>
<html>
<head>
  <script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/2.1.3/jquery.min.js"></script>
  <!--  Include leaflet javascript and css -->
  <link rel="stylesheet" href="https://unpkg.com/leaflet@1.0.3/dist/leaflet.css" crossorigin="">
  <script src="https://unpkg.com/leaflet@1.0.3/dist/leaflet-src.js" crossorigin=""></script>
  <!--  Include angular stuff  -->
  <script src="https://cdnjs.cloudflare.com/ajax/libs/angular.js/1.5.8/angular.js"></script>
  <script src="https://cdnjs.cloudflare.com/ajax/libs/angular-material/1.0.9/angular-material.min.js"></script>
  <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/angular-material/1.0.9/angular-material.min.css">
  <link rel="stylesheet" href="https://fonts.googleapis.com/icon?family=Material+Icons">
  <script src="https://cdnjs.cloudflare.com/ajax/libs/angular.js/1.5.8/angular-animate.js"></script>
  <script src="https://cdnjs.cloudflare.com/ajax/libs/angular.js/1.5.8/angular-aria.js"></script>
  <!--  Include r360.js and angular plugin -->
  <script src="https://releases.route360.net/r360-js/latest.js"></script>
  <script src="https://releases.route360.net/r360-angular/latest.js"></script>
  <style>
    html, body { width: 100%; height: 100%; margin: 0; font-family: sans-serif; }
    #map { width: 100%; height: 100%; }
    .input-group .form-control { border: none; border-shadow: none; height: 43px; }
    form { position: absolute; left: 5px; top: 5px; min-width: 350px; }
    .controls { position: absolute; top: 20px; left: 20px; right: 20px; z-index: 1000;}
    .md-virtual-repeat-container.md-autocomplete-suggestions-container { z-index: 1000; }
  </style>
</head>

<body>
  <!--  where the map will live  -->
  <div id="map"></div>

  <div ng-app="TravelTypeExample" ng-cloak="" ng-controller="TravelTypeExampleController as vm" class="controls" layout="column" layout-align="start end">
    <form flex="" ng-submit="$event.preventDefault()">
      <md-autocomplete md-no-cache="vm.autocomplete.noCache" md-selected-item="vm.autocomplete.selectedItem" md-search-text="vm.autocomplete.searchText" md-selected-item-change="vm.autocomplete.select(item)" md-items="item in vm.autocomplete.query(vm.autocomplete.searchText)" md-item-text="item.description.full" md-min-length="3" placeholder="Select Start..." md-menu-class="r360-autocomplete">
        <md-item-template>
          <span class="item-title">
            <span><strong>{{item.description.title}}</strong></span>
          </span>
          <span class="item-metadata">
            <span class="item-metastat">{{item.description.meta1}}</span>
            <span class="item-metastat">{{item.description.meta2}}</span>
          </span>
        </md-item-template>
        <md-not-found>No matches found for &quot;{{vm.autocomplete.searchText}}&quot;.
        </md-not-found>
      </md-autocomplete>
    </form>
    <travel-type-fab label="Travel Type" md-direction="left" md-animation="md-scale" model="vm.travelType">
    </travel-type-fab>
  </div>
  
  <script>
    // create a marker and but dont add it to the map yet
    var marker;
    var map;
    var polygonLayer;

    function initMap() {
      // add the map and set the initial center to berlin
      map = L.map('map', { zoomControl: false }).setView([52.51, 13.37], 13);
      map.attributionControl.addAttribution("ÖPNV Daten © <a href='https://www.vbb.de/de/index.html' target='_blank'>VBB</a>");

      // initialise the base map
      r360.basemap({ style: 'basic', apikey: '__APIPLACEHOLDER__' }).addTo(map);

      // create the layer to add the polygons
      polygonLayer = r360.leafletPolygonLayer().addTo(map);
    };

    function showPolygons(travelType) {
      // you need to define some options for the polygon service
      // for more travel options check out the other tutorials
      var travelOptions = r360.travelOptions();

      // please contact us and request your own key
      travelOptions.setServiceKey('__APIPLACEHOLDER__');
      // set the service url for your area
      travelOptions.setServiceUrl('https://service.route360.net/germany/');

      // we only have one source which is the marker we just added
      travelOptions.addSource(marker);
      // we want to have polygons for 10 to 30 minutes
      travelOptions.setTravelTimes([600, 1200, 1800]);
      // set the travel type
      travelOptions.setTravelType(travelType)

      // call the service
      r360.PolygonService.getTravelTimePolygons(travelOptions, function(polygons) {

        // in case there are already polygons on the map/layer clear them
        // and fit the map to the polygon bounds ('true' parameter)
        polygonLayer.clearAndAddLayers(polygons, true);
      });
    };

    // create an auto complete control and set it to return only german cities, streets etc.
    // add an image which will be displayed next to the users input to know which markers
    // belong to which auto complete & and add a reset button to delete user inputs
    angular.module('TravelTypeExample',['ngMaterial', 'ng360'])
    .controller("TravelTypeExampleController", function($scope, R360Util) {
      initMap();

      var vm = this;

      this.travelType = 'walk';

      function query(q) {
        var center = map.getCenter();

        return R360Util.geocode(q, center)
        .then(function(results) {
          var filteredResults = results.filter(function(item) {
            return item.properties.country && (item.properties.country.toLowerCase() == "germany")
          })

          return filteredResults
        });
      }

      function selectItem(item) {
        if (!item) {
          return;
        }

        // if the source marker is on the map already, move it to the new position
        if (map.hasLayer(marker)) {
          marker.setLatLng([item.geometry.coordinates[1], item.geometry.coordinates[0]]);
        // otherwise add it and attach the action for marker dragging
        } else {

          // add a marker to the map
          marker = L.marker(([item.geometry.coordinates[1], item.geometry.coordinates[0]]), { draggable: true }).addTo(map);

          // we need to update some stuff on the 'dragend' action of the marker
          marker.on('dragend', function() {

            // redraw the polygons
            showPolygons(vm.travelType);

            R360Util.reverseGeocode(marker.getLatLng(), true).then(function(response) {
              var displayName = R360Util.buildPlaceDescription(response.properties).full;
              vm.autocomplete.searchText = displayName;
              marker.bindPopup(displayName);
            });
          });
        }

        // and show the polygons for the new source
        marker.bindPopup(item.firstRow);
        showPolygons(vm.travelType);
      }

      vm.autocomplete = { query: query, select: selectItem, }

      $scope.$watch('vm.travelType', function() {
        if (vm.travelType && marker) {
          showPolygons(vm.travelType);
        }
      });
    });
  </script>
</body>
</html>