Поиск по категориям в геокодере Mapbox GL

#mapbox #openstreetmap #geocoding #mapbox-gl-js

#mapbox #openstreetmap #геокодирование #mapbox-gl-js

Вопрос:

Как выполнить общий поиск с помощью геокодера mapbox gl? Под «общим» я подразумеваю поиск по типу места, а не по конкретному месту, например, «кафе». Я хочу иметь возможность выполнять поиск по «кафе» или «продуктовому магазину» или любой другой общей категории, и чтобы карта помечала маркером каждое место такого типа, найденное в текущем окне просмотра. Не похоже, что поддерживаются общие поисковые запросы или любой поиск, который возвращает несколько результатов. Это правильно? Это было бы немного удивительно, поскольку OpenStreetMap, похоже, располагает данными, необходимыми для поддержки такого рода поиска. Я включил типы: ‘poi’ и функцию фильтра, которая удаляет потенциальные результаты, если в их строке obj.properties.category нет «кафе» или «кофе». Это уменьшает шум в предложениях результатов, но когда пользователь нажимает ENTER, результат по-прежнему относится только к одному месту (независимо от того, какое из предложений было выделено). В примерах документации нет примеров такого рода общего поиска.

Обратите внимание, что в этом примере результаты уже ограничены пределами Нью-Йорка с помощью ограничивающей рамки. Также обратите внимание, что вам пришлось бы вставить свой собственный mapboxgl.accessToken, чтобы заставить этот пример работать.

Вот рабочая базовая настройка react mapbox gl:

 import React from 'react';
import mapboxgl from 'mapbox-gl';
import 'mapbox-gl/dist/mapbox-gl.css'
import Geocoder from "@mapbox/mapbox-gl-geocoder";
import "@mapbox/mapbox-gl-geocoder/dist/mapbox-gl-geocoder.css";
import './map.scss';

mapboxgl.accessToken = 'YOUR_ACCESS_TOKEN_HERE';
const nyc_bbox = [-74.04848, 40.54217, -73.72479, 41.15114]

export class Map extends React.Component {
  constructor(props) {
    super(props);
    
    this.state = {
      lng: -73.9392,
      lat: 40.8053,
      zoom: 14
    };
  }

  componentDidMount() {
    const map = new mapboxgl.Map({
      container: this.mapContainer,
      style: 'mapbox://styles/mapbox/streets-v11',
      center: [this.state.lng, this.state.lat],
      zoom: this.state.zoom,
      zoomAnimation: false
    });

    const geocoder = new Geocoder({
      accessToken: mapboxgl.accessToken,
      mapboxgl: mapboxgl,
      bbox: nyc_bbox,
      types: 'poi'
    })
    
    map.addControl(geocoder);
  }

  render() {
    return <div ref={el => this.mapContainer = el} className='mapContainer'/>
  }
}
  

И прилагаемый scss-файл (./map.scss):

 .mapContainer {
    position: absolute;
    top: 0;
    right: 0;
    left: 0;
    bottom: 0;
  }
  

А также тот ./index.js файл:

 import React from 'react';
import ReactDOM from 'react-dom';
import { Map } from './map';

class Application extends React.Component {
  constructor(props) {
    super(props);
  }

  render() {
    return <Map />
  }
}

ReactDOM.render(
  <Application />,
  document.getElementById('app'));
  

Спасибо.

Ответ №1:

Конечно, это возможно. Вы можете передать poi в качестве значения для types и определить filter функцию, которая проверяет categories свойство каждого результата, чтобы решить, сохранять его или нет.

Комментарии:

1. Куда вы вставляете функцию фильтрации? Это то, что вы бы ввели в функцию arrow здесь? geocoder.on(‘результат’, (e) => {} )

2. Смотрите документацию github.com/mapbox/mapbox-gl-geocoder/blob/master/API.md

3. Определение функции фильтра позволяет фильтровать то, что отображается в качестве предлагаемого результата в раскрывающемся списке геокодер, но это не позволяет нам ставить маркер на все результаты одновременно. Единственный раз, когда у нас, похоже, есть доступ к нескольким результатам одновременно, — это метод geocoder.on(‘results’, обратный вызов). Но как только пользователь нажмет enter, он выполнит выделенное предложение результата в геокодере и удалит остальное. Как мы можем поместить маркеры на все результаты?

4. Правильно, включите прослушиватель в results событие и затем поместите их все на карту. В чем проблема с этим?

5. Ах, прошу прощения. Я неправильно понял суть вопроса, был отвлечен техническими деталями. Вы пытаетесь выполнить поиск по «всем кафе в этом районе»? Не используйте геокодер, просто используйте mapbox-streets векторные плитки и фильтр для кафе. Это не идеально и вообще не будет работать при низком масштабировании.

Ответ №2:

Это возможно сделать, но это надуманно. Давайте возьмем «кафе» в качестве примера поиска по категориям. Предложение Стива Беннетта использовать mapbox-streets (в комментариях к его ответу) кажется правильным, но я документирую свой ответ здесь в качестве альтернативы на случай, если это будет полезно кому-либо еще.

Шаг 1: Добавьте пустой массив поисковых запросов для поискового запроса, который я хочу поддерживать (в данном случае, поиск по «кафе»). Мне также нужны долгота и широта.

 this.state = {
  cafes: [],
  lng: -74,
  lat: 40.8
}
  

Шаг 2. Добавьте функцию localGeocoder и верните результат для вашего поискового запроса, чтобы он отображался в предложениях, и пользователь мог нажать «enter» для него в поле ввода геокодера. Установите местоположение этой точки в качестве текущего центра карты, чтобы она не перемещалась при выборе результата «кафе».

 const geocoder = new Geocoder({
  accessToken: mapboxgl.accessToken,
  mapboxgl: mapboxgl,
  marker: false,
  localGeocoder: this.localGeocodingFunc
})

// ...

  localGeocodingFunc(query) {
    if (query !== 'cafe') {
      return [];
    }
    const cafe_result = {
      type: 'General cafe search',
      properties: {
        title: 'generic cafe',
        description: "Generic cafe search trigger"
      },
      context: [],
      geometry: {
        coordinates: [this.state.lng, this.state.lat],
        type: 'Point'
      },
      place_name: 'cafe',
      text: 'cafe',
      center: [this.state.lng, this.state.lat],
      place_type: ['cafe']
    }
    return [cafe_result];
  }
  

Шаг 3. Каждый раз, когда пользователь перемещает карту, обновляйте уровень масштабирования геокодера, чтобы он соответствовал уровню карты, чтобы не изменять уровень масштабирования при выборе результата «кафе». Я также счел полезным установить ограничивающую рамку, чтобы в текущем окне просмотра отображались только результаты cafe.

 map.on('move', () => {
  geocoder.setZoom(map.getZoom());
  geocoder.setBbox(this.viewportToBbox(map));
});

//...

  viewportToBbox(_map) {
    // Return the current viewport in a bbox array format.
    let {_sw, _ne} = _map.getBounds();
    return [_sw.lng, _sw.lat, _ne.lng, _ne.lat];
  }
  

Шаг 4: Прослушайте результат «кафе», и если он появится, затем верните кафе, перечисленные в this.state.cafes. Я использую команду jquery здесь, чтобы удалить все маркеры, находящиеся в данный момент на карте, что предотвращает накопление маркеров, если вы выполняете поиск по этой категории несколько раз.

 geocoder.on('result', (e) => {
  console.log('onResult e value:', e);
  $( ".marker" ).remove();
  if (e.result.text === 'cafe') {
    if (this.state.cafes.length > 0) {
      // Process these cafes however you want to put a marker on each one
    }
  }
}
  

Так что это почти наверняка не так элегантно, как предложение Стива Беннетта использовать mapbox-streets, но я еще не вник в это, и это, по крайней мере, работает.