如何以编程方式触发 Mapbox GL JS Geocoder?

3
我正在使用Mapbox GL JS制作一个简单的商店查找地图。除了其他用例之外,我希望用户能够单击地理位置图标,将用户的位置传递给地理编码器并触发查询。
我在函数的触发部分卡住了。我可以将地理位置结果传递给地理编码器输入,但是我无法触发地理编码器函数。
目前,我在设置地理编码器输入值后触发按键按下事件。这应该会触发地理编码操作,不是吗?
我看到文档中提到了setinput和query方法,但是我没有看到如何使用它们的强大示例。
换句话说,我的问题是,在设置其输入值后如何以编程方式触发地理编码器函数?
这是我的当前代码:
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();

你想触发地理编码控件以显示建议列表,还是希望它进入并选择第一个?地理编码控件是为人类使用而设计的,如果您只想通过编程方式进行地理编码或反向地理编码,则 Mapbox SDK for JS 更好 https://github.com/mapbox/mapbox-sdk-js。 - AndrewHarvey
@AndrewHarvey 我更喜欢选择第一个结果。商店之间的地理距离并不是必须精确的。那么,你是说使用Mapbox GL JS无法直接实现我希望实现的功能吗?如果可能的话,我希望不用为这个单一的功能重写SDK。但是,我很感谢你的建议。谢谢。 - dentalhero
1个回答

2

看起来未来的版本会有更新计划,可以在这里查看。

同时,以下方法对我有效:

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

显然,只需将“New York”更改为您用于位置的任何变量。

您还可以尝试以下操作,但对我来说不太一致:

geocoder.query('New York');

第一个问题,有没有一种方法可以将 flyTo 设置为输入的位置? - Edwards

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接