dentalhero
dentalhero

Reputation: 699

How do I programmatically trigger Mapbox GL JS Geocoder?

I am using Mapbox GL JS to produce a simple store finder map. Among other use cases, I want the user to be able to click the geolocation icon, pass the user's location to the geocoder and trigger the query.

I'm stuck on the trigger portion of the function. I can pass the geolocation results to the geocoder input, but I am unable to then trigger the geocoder function.

Currently, I'm triggering a keydown event after setting the value of the geocoder input to that returned by my geolocation function. This SHOULD trigger the geocode operations, no?

I see that there's a setinput and query method mentioned in the documentation, but I don't see robust examples of how to use them.

So, to be clear, my question is, how do I trigger the geocoder function programmatically after setting its input value?

Here's my current code:

let storefinder_module = (function () {
  "use strict";

  const DOM = {};

  /* =================== private methods ================= */
  function cacheDom() {}

  function storeMap() {
    if (!("remove" in Element.prototype)) {
      Element.prototype.remove = function () {
        if (this.parentNode) {
          this.parentNode.removeChild(this);
        }
      };
    }

    mapboxgl.accessToken =
      "removed";

    /**
     * Add the map to the page
     */
    const map = new mapboxgl.Map({
      container: "map",
      style: "mapbox://styles/mapbox/light-v10",
      center: [-77.034084142948, 38.909671288923],
      zoom: 13,
      scrollZoom: false,
    });

    const stores = {
      type: "FeatureCollection",
      features: [
        {
          type: "Feature",
          geometry: {
            type: "Point",
            coordinates: [-77.034084142948, 38.909671288923],
          },
          properties: {
            phoneFormatted: "(202) 234-7336",
            phone: "2022347336",
            address: "1471 P St NW",
            city: "Washington DC",
            country: "United States",
            crossStreet: "at 15th St NW",
            postalCode: "20005",
            state: "D.C.",
          },
        },
        {
          type: "Feature",
          geometry: {
            type: "Point",
            coordinates: [-77.049766, 38.900772],
          },
          properties: {
            phoneFormatted: "(202) 507-8357",
            phone: "2025078357",
            address: "2221 I St NW",
            city: "Washington DC",
            country: "United States",
            crossStreet: "at 22nd St NW",
            postalCode: "20037",
            state: "D.C.",
          },
        },
        {
          type: "Feature",
          geometry: {
            type: "Point",
            coordinates: [-77.043929, 38.910525],
          },
          properties: {
            phoneFormatted: "(202) 387-9338",
            phone: "2023879338",
            address: "1512 Connecticut Ave NW",
            city: "Washington DC",
            country: "United States",
            crossStreet: "at Dupont Circle",
            postalCode: "20036",
            state: "D.C.",
          },
        },
        {
          type: "Feature",
          geometry: {
            type: "Point",
            coordinates: [-77.0672, 38.90516896],
          },
          properties: {
            phoneFormatted: "(202) 337-9338",
            phone: "2023379338",
            address: "3333 M St NW",
            city: "Washington DC",
            country: "United States",
            crossStreet: "at 34th St NW",
            postalCode: "20007",
            state: "D.C.",
          },
        },
        {
          type: "Feature",
          geometry: {
            type: "Point",
            coordinates: [-77.002583742142, 38.887041080933],
          },
          properties: {
            phoneFormatted: "(202) 547-9338",
            phone: "2025479338",
            address: "221 Pennsylvania Ave SE",
            city: "Washington DC",
            country: "United States",
            crossStreet: "btwn 2nd & 3rd Sts. SE",
            postalCode: "20003",
            state: "D.C.",
          },
        },
        {
          type: "Feature",
          geometry: {
            type: "Point",
            coordinates: [-76.933492720127, 38.99225245786],
          },
          properties: {
            address: "8204 Baltimore Ave",
            city: "College Park",
            country: "United States",
            postalCode: "20740",
            state: "MD",
          },
        },
        {
          type: "Feature",
          geometry: {
            type: "Point",
            coordinates: [-77.097083330154, 38.980979],
          },
          properties: {
            phoneFormatted: "(301) 654-7336",
            phone: "3016547336",
            address: "4831 Bethesda Ave",
            cc: "US",
            city: "Bethesda",
            country: "United States",
            postalCode: "20814",
            state: "MD",
          },
        },
        {
          type: "Feature",
          geometry: {
            type: "Point",
            coordinates: [-77.359425054188, 38.958058116661],
          },
          properties: {
            phoneFormatted: "(571) 203-0082",
            phone: "5712030082",
            address: "11935 Democracy Dr",
            city: "Reston",
            country: "United States",
            crossStreet: "btw Explorer & Library",
            postalCode: "20190",
            state: "VA",
          },
        },
        {
          type: "Feature",
          geometry: {
            type: "Point",
            coordinates: [-77.10853099823, 38.880100922392],
          },
          properties: {
            phoneFormatted: "(703) 522-2016",
            phone: "7035222016",
            address: "4075 Wilson Blvd",
            city: "Arlington",
            country: "United States",
            crossStreet: "at N Randolph St.",
            postalCode: "22203",
            state: "VA",
          },
        },
        {
          type: "Feature",
          geometry: {
            type: "Point",
            coordinates: [-75.28784, 40.008008],
          },
          properties: {
            phoneFormatted: "(610) 642-9400",
            phone: "6106429400",
            address: "68 Coulter Ave",
            city: "Ardmore",
            country: "United States",
            postalCode: "19003",
            state: "PA",
          },
        },
        {
          type: "Feature",
          geometry: {
            type: "Point",
            coordinates: [-75.20121216774, 39.954030175164],
          },
          properties: {
            phoneFormatted: "(215) 386-1365",
            phone: "2153861365",
            address: "3925 Walnut St",
            city: "Philadelphia",
            country: "United States",
            postalCode: "19104",
            state: "PA",
          },
        },
        {
          type: "Feature",
          geometry: {
            type: "Point",
            coordinates: [-77.043959498405, 38.903883387232],
          },
          properties: {
            phoneFormatted: "(202) 331-3355",
            phone: "2023313355",
            address: "1901 L St. NW",
            city: "Washington DC",
            country: "United States",
            crossStreet: "at 19th St",
            postalCode: "20036",
            state: "D.C.",
          },
        },
      ],
    };

    /**
     * Assign a unique id to each store. You'll use this `id`
     * later to associate each point on the map with a listing
     * in the sidebar.
     */
    stores.features.forEach(function (store, i) {
      store.properties.id = i;
    });

    /**
     * Wait until the map loads to make changes to the map.
     */
    map.on("load", function (e) {
      /**
       * This is where your '.addLayer()' used to be, instead
       * add only the source without styling a layer
       */
      map.addSource("places", {
        type: "geojson",
        data: stores,
      });

      /**
       * Create a new MapBoxDirections instance.
       */
      //var directions = new MapboxDirections({
      //  accessToken: mapboxgl.accessToken,
      // mapboxgl: mapboxgl,
      //});

      // Create a new MapboxGeocoder instance.
      const geocoder = new MapboxGeocoder({
        accessToken: mapboxgl.accessToken,
        mapboxgl: mapboxgl,
        zoom: 4,
        clearAndBlurOnEsc: true,
        placeholder: "Enter Your Location (Zip or Address)",
      });

      // Add geolocate control to the map.
      const geolocate = new mapboxgl.GeolocateControl({
        positionOptions: {
          enableHighAccuracy: true,
        },
        trackUserLocation: false,
      });

      function getAddress(lon, lat) {
        let longitude = lon;
        let latitude = lat;
        let apiCoordtoAddress =
          "https://api.mapbox.com/geocoding/v5/mapbox.places/" +
          longitude +
          "," +
          latitude +
          ".json?access_token=" +
          mapboxgl.accessToken;
        let apiCoordtoAddressEncoded = encodeURI(apiCoordtoAddress);

        $.getJSON(apiCoordtoAddressEncoded, function (data, geocoder) {
          let dataAddress = data.features[0].place_name;
          let $geoCoderInput = $("#geocoder .mapboxgl-ctrl-geocoder--input");

          $geoCoderInput.val("20814");
          $geoCoderInput.keydown();

        });
      }

      /**
       * Add all the things to the page:
       * - The location listings on the side of the page
       * - The search box (MapboxGeocoder) onto the map
       * - The markers onto the map
       */
      buildLocationList(stores);
      document.getElementById("geocoder").appendChild(geocoder.onAdd(map));
      addMarkers();
      map.addControl(geolocate);

      // Geolocate Event
      geolocate.on("geolocate", function (e) {
        let lon = e.coords.longitude;
        let lat = e.coords.latitude;
        getAddress(lon, lat);
      });

      /**
       * Listen for when a geocoder result is returned. When one is returned:
       * - Calculate distances
       * - Sort stores by distance
       * - Rebuild the listings
       * - Adjust the map camera
       * - Open a popup for the closest store
       * - Highlight the listing for the closest store.
       */
      geocoder.on("result", function (ev) {
        /* Get the coordinate of the search result */
        var searchResult = ev.result.geometry;

        /**
         * Calculate distances:
         * For each store, use turf.distance to calculate the distance
         * in miles between the searchResult and the store. Assign the
         * calculated value to a property called `distance`.
         */
        var options = { units: "miles" };
        stores.features.forEach(function (store) {
          Object.defineProperty(store.properties, "distance", {
            value: turf.distance(searchResult, store.geometry, options),
            writable: true,
            enumerable: true,
            configurable: true,
          });
        });

        /**
         * Sort stores by distance from closest to the `searchResult`
         * to furthest.
         */
        stores.features.sort(function (a, b) {
          if (a.properties.distance > b.properties.distance) {
            return 1;
          }
          if (a.properties.distance < b.properties.distance) {
            return -1;
          }
          return 0; // a must be equal to b
        });

        /**
         * Rebuild the listings:
         * Remove the existing listings and build the location
         * list again using the newly sorted stores.
         */
        let listings = document.getElementById("listings");
        while (listings.firstChild) {
          listings.removeChild(listings.firstChild);
        }
        buildLocationList(stores);
        /* Open a popup for the closest store. */
        createPopUp(stores.features[0]);

        /** Highlight the listing for the closest store. */
        let activeListing = document.getElementById(
          "listing-" + stores.features[0].properties.id
        );
        activeListing.classList.add("active");
      });
    });

    /**
     * Add a marker to the map for every store listing.
     **/
    function addMarkers() {
      /* For each feature in the GeoJSON object above: */
      stores.features.forEach(function (marker) {
        /* Create a div element for the marker. */
        const el = document.createElement("div");
        /* Assign a unique `id` to the marker. */
        el.id = "marker-" + marker.properties.id;
        /* Assign the `marker` class to each marker for styling. */
        el.className = "marker";

        /**
         * Create a marker using the div element
         * defined above and add it to the map.
         **/
        new mapboxgl.Marker(el, { offset: [0, -23] })
          .setLngLat(marker.geometry.coordinates)
          .addTo(map);

        /**
         * Listen to the element and when it is clicked, do three things:
         * 1. Fly to the point
         * 2. Close all other popups and display popup for clicked store
         * 3. Highlight listing in sidebar (and remove highlight for all other listings)
         **/
        el.addEventListener("click", function (e) {
          flyToStore(marker);
          createPopUp(marker);
          var activeItem = document.getElementsByClassName("active");
          e.stopPropagation();
          if (activeItem[0]) {
            activeItem[0].classList.remove("active");
          }
          let listing = document.getElementById(
            "listing-" + marker.properties.id
          );
          listing.classList.add("active");
        });
      });
    }

    /**
     * Add a listing for each store to the sidebar.
     **/
    function buildLocationList(data) {
      data.features.forEach(function (store, i) {
        /**
         * Create a shortcut for `store.properties`,
         * which will be used several times below.
         **/
        let prop = store.properties;

        /* Add a new listing section to the sidebar. */
        const listings = document.getElementById("listings");
        const listing = listings.appendChild(document.createElement("div"));
        /* Assign a unique `id` to the listing. */
        listing.id = "listing-" + prop.id;
        /* Assign the `item` class to each listing for styling. */
        listing.className = "item";

        /* Add the link to the individual listing created above. */
        const link = listing.appendChild(document.createElement("a"));
        link.href = "#";
        link.className = "title";
        link.id = "link-" + prop.id;
        link.innerHTML = prop.address;

        /* Add details to the individual listing. */
        const details = listing.appendChild(document.createElement("div"));
        details.innerHTML = prop.city;
        if (prop.phone) {
          details.innerHTML += " &middot; " + prop.phoneFormatted;
        }
        if (prop.distance) {
          let roundedDistance = Math.round(prop.distance * 100) / 100;
          details.innerHTML +=
            "<p><strong>" + roundedDistance + " miles away</strong></p>";
        }

        /**
         * Listen to the element and when it is clicked, do four things:
         * 1. Update the `currentFeature` to the store associated with the clicked link
         * 2. Fly to the point
         * 3. Close all other popups and display popup for clicked store
         * 4. Highlight listing in sidebar (and remove highlight for all other listings)
         **/
        link.addEventListener("click", function (e) {
          for (var i = 0; i < data.features.length; i++) {
            if (this.id === "link-" + data.features[i].properties.id) {
              let clickedListing = data.features[i];
              flyToStore(clickedListing);
              createPopUp(clickedListing);
            }
          }
          const activeItem = document.getElementsByClassName("active");
          if (activeItem[0]) {
            activeItem[0].classList.remove("active");
          }
          this.parentNode.classList.add("active");
        });
      });
    }

    /**
     * Use Mapbox GL JS's `flyTo` to move the camera smoothly
     * a given center point.
     **/
    function flyToStore(currentFeature) {
      map.flyTo({
        center: currentFeature.geometry.coordinates,
        zoom: 15,
      });
    }

    /**
     * Create a Mapbox GL JS `Popup`.
     **/
    function createPopUp(currentFeature) {
      const popUps = document.getElementsByClassName("mapboxgl-popup");
      if (popUps[0]) popUps[0].remove();

      var popup = new mapboxgl.Popup({
        closeOnClick: false,
      })
        .setLngLat(currentFeature.geometry.coordinates)
        .setHTML(
          "<h3>Sweetgreen</h3>" +
            "<h4>" +
            currentFeature.properties.address +
            "</h4>"
        )
        .addTo(map);
    }

    /* given a query in the form "lng, lat" or "lat, lng" returns the matching
     * geographic coordinate(s) as search results in carmen geojson format,
     * https://github.com/mapbox/carmen/blob/master/carmen-geojson.md
     */
    let coordinatesGeocoder = function (query) {
      // match anything which looks like a decimal degrees coordinate pair
      let matches = query.match(
        /^[ ]*(?:Lat: )?(-?\d+\.?\d*)[, ]+(?:Lng: )?(-?\d+\.?\d*)[ ]*$/i
      );
      if (!matches) {
        return null;
      }

      function coordinateFeature(lng, lat) {
        return {
          center: [lng, lat],
          geometry: {
            type: "Point",
            coordinates: [lng, lat],
          },
          place_name: "Lat: " + lat + " Lng: " + lng,
          place_type: ["coordinate"],
          properties: {},
          type: "Feature",
        };
      }

      let coord1 = Number(matches[1]);
      let coord2 = Number(matches[2]);
      const geocodes = [];

      if (coord1 < -90 || coord1 > 90) {
        // must be lng, lat
        geocodes.push(coordinateFeature(coord1, coord2));
      }

      if (coord2 < -90 || coord2 > 90) {
        // must be lat, lng
        geocodes.push(coordinateFeature(coord2, coord1));
      }

      if (geocodes.length === 0) {
        // else could be either lng, lat or lat, lng
        geocodes.push(coordinateFeature(coord1, coord2));
        geocodes.push(coordinateFeature(coord2, coord1));
      }

      return geocodes;
    };
  }

  // render DOM

  /* =================== public methods ================== */
  function init() {
    cacheDom();
    storeMap();
  }

  /* =============== export public methods =============== */
  return {
    init: init,
  };
})();


storefinder_module.init();

Upvotes: 4

Views: 2869

Answers (1)

user3175632
user3175632

Reputation: 31

It looks like there is a plan to update this in a future release viewable here

In the meantime, doing the following worked for me:

geocoder.setInput('New York')._geocode('New York');

Obviously, just change 'New York to whatever variable you're using for the location.

You can also do the following, though it was less consistent for me:

geocoder.query('New York');

Upvotes: 2

Related Questions