Как графически отображать данные JSON на карте Leaflet?

Я хочу использовать API Citybik.es (http://api.citybik.es/), чтобы показать данные на карте листовки.

Как реализовать эти данные на карте из JSON? Я уже получаю информацию из API внутри componentDidMount(), но понятия не имею, как поместить эти данные на карту.

Ответ выглядит примерно так:

{
  "networks": [
{
  "company": [
    "Bike U Sp. z o.o."
  ], 
  "href": "/v2/networks/bbbike", 
  "id": "bbbike", 
  "location": {
    "city": "Bielsko-Bia\u0142a", 
    "country": "PL", 
    "latitude": 49.8225, 
    "longitude": 19.044444
  }, 
  "name": "BBBike"
}, 
{
  "company": [
    "PBSC", 
    "Alta Bicycle Share, Inc"
  ], 
  "href": "/v2/networks/melbourne-bike-share", 
  "id": "melbourne-bike-share", 
  "location": {
    "city": "Melbourne", 
    "country": "AU", 
    "latitude": -37.814107, 
    "longitude": 144.96328
  }, 
  "name": "Melbourne Bike Share"
}
}

Спасибо за помощь!

import React, { Component } from 'react';
import L from 'leaflet';
import { Map, TileLayer, Marker, Popup } from 'react-leaflet';

// code for map marker icon
var myIcon = L.icon({
    iconUrl: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABkAAAApCAYAAADAk4LOAAAFgUlEQVR4Aa1XA5BjWRTN2oW17d3YaZtr2962HUzbDNpjszW24mRt28p47v7zq/bXZtrp/lWnXr337j3nPCe85NcypgSFdugCpW5YoDAMRaIMqRi6aKq5E3YqDQO3qAwjVWrD8Ncq/RBpykd8oZUb/kaJutow8r1aP9II0WmLKLIsJyv1w/kqw9Ch2MYdB++12Onxee/QMwvf4/Dk/Lfp/i4nxTXtOoQ4pW5Aj7wpici1A9erdAN2OH64x8OSP9j3Ft3b7aWkTg/Fm91siTra0f9on5sQr9INejH6CUUUpavjFNq1B+Oadhxmnfa8RfEmN8VNAsQhPqF55xHkMzz3jSmChWU6f7/XZKNH+9+hBLOHYozuKQPxyMPUKkrX/K0uWnfFaJGS1QPRtZsOPtr3NsW0uyh6NNCOkU3Yz+bXbT3I8G3xE5EXLXtCXbbqwCO9zPQYPRTZ5vIDXD7U+w7rFDEoUUf7ibHIR4y6bLVPXrz8JVZEql13trxwue/uDivd3fkWRbS6/IA2bID4uk0UpF1N8qLlbBlXs4Ee7HLTfV1j54APvODnSfOWBqtKVvjgLKzF5YdEk5ewRkGlK0i33Eofffc7HT56jD7/6U+qH3Cx7SBLNntH5YIPvODnyfIXZYRVDPqgHtLs5ABHD3YzLuespb7t79FY34DjMwrVrcTuwlT55YMPvOBnRrJ4VXTdNnYug5ucHLBjEpt30701A3Ts+HEa73u6dT3FNWwflY86eMHPk+Yu+i6pzUpRrW7SNDg5JHR4KapmM5Wv2E8Tfcb1HoqqHMHU+uWDD7zg54mz5/2BSnizi9T1Dg4QQXLToGNCkb6tb1NU+QAlGr1++eADrzhn/u8Q2YZhQVlZ5+CAOtqfbhmaUCS1ezNFVm2imDbPmPng5wmz+gwh+oHDce0eUtQ6OGDIyR0uUhUsoO3vfDmmgOezH0mZN59x7MBi++WDL1g/eEiU3avlidO671bkLfwbw5XV2P8Pzo0ydy4t2/0eu33xYSOMOD8hTf4CrBtGMSoXfPLchX+J0ruSePw3LZeK0juPJbYzrhkH0io7B3k164hiGvawhOKMLkrQLyVpZg8rHFW7E2uHOL888IBPlNZ1FPzstSJM694fWr6RwpvcJK60+0HCILTBzZLFNdtAzJaohze60T8qBzyh5ZuOg5e7uwQppofEmf2++DYvmySqGBuKaicF1blQjhuHdvCIMvp8whTTfZzI7RldpwtSzL+F1+wkdZ2TBOW2gIF88PBTzD/gpeREAMEbxnJcaJHNHrpzji0gQCS6hdkEeYt9DF/2qPcEC8RM28Hwmr3sdNyht00byAut2k3gufWNtgtOEOFGUwcXWNDbdNbpgBGxEvKkOQsxivJx33iow0Vw5S6SVTrpVq11ysA2Rp7gTfPfktc6zhtXBBC+adRLshf6sG2RfHPZ5EAc4sVZ83yCN00Fk/4kggu40ZTvIEm5g24qtU4KjBrx/BTTH8ifVASAG7gKrnWxJDcU7x8X6Ecczhm3o6YicvsLXWfh3Ch1W0k8x0nXF+0fFxgt4phz8QvypiwCCFKMqXCnqXExjq10beH+UUA7+nG6mdG/Pu0f3LgFcGrl2s0kNNjpmoJ9o4B29CMO8dMT4Q5ox8uitF6fqsrJOr8qnwNbRzv6hSnG5wP+64C7h9lp30hKNtKdWjtdkbuPA19nJ7Tz3zR/ibgARbhb4AlhavcBebmTHcFl2fvYEnW0ox9xMxKBS8btJ+KiEbq9zA4RthQXDhPa0T9TEe69gWupwc6uBUphquXgf+/FrIjweHQS4/pduMe5ERUMHUd9xv8ZR98CxkS4F2n3EUrUZ10EYNw7BWm9x1GiPssi3GgiGRDKWRYZfXlON+dfNbM+GgIwYdwAAAAASUVORK5CYII=',
    iconSize: [25, 41],
    iconAnchor: [12.5, 41],
    popupAnchor: [0, -41]
});

class App extends Component {
    state = {
        location: {
            lat: 51.505,
            lng: -0.09,
        },
        haveUsersLocation: false,
        zoom: 2,
        networks: null
    }

    //lifecycle method to get the user's current position(if they so desire).
    componentDidMount() {

        fetch('https://api.citybik.es/v2/networks')
        .then(res => res.json())
        .then(response => {
            console.log("response", response);
            console.log(response.networks)
            const networkData = response.networks
            const networkList = networkData.map((data) => {
                console.log(data);
            })
    })


        navigator.geolocation.getCurrentPosition((position) => {
            this.setState({
               location: {
                   lat: position.coords.latitude,
                   lng: position.coords.longitude
               },
               haveUsersLocation: true,
               zoom: 13
            });
        }, () => {
            console.log('Uops! The user didnt give its location!');
            fetch('https://ipapi.co/json')
            .then(res => res.json())
            .then(location => {
                this.setState({
                    location: {
                        lat: location.latitude,
                        lng: location.longitude
                    },
                    haveUsersLocation: true,
                    zoom: 13
                 });
            })
        });
    }


    render() {
        const position = [this.state.location.lat, this.state.location.lng]
        return (
            <Map className="map" center={position} zoom={this.state.zoom}>
                <TileLayer
                    attribution="&amp;copy <a href=&quot;http://osm.org/copyright&quot;>OpenStreetMap</a> contributors"
                    url="https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png"
                />
                {
                    this.state.haveUsersLocation ? 
                    <Marker
                        position={position}
                        icon={myIcon}>
                    <Popup>
                        A pretty CSS3 popup. <br /> Easily customizable.
                    </Popup>
                </Marker> : ''
                }
            </Map>
        )
    }
}

ReactDOM.render(<App/>,
    document.getElementById('root')
);
body {
    margin: 0px;
    width: 100vw;
    height: 100vh;
}

#root {
    height: 100%;
}

.map {
    height: 100%;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
<link href="https://unpkg.com/[email protected]/dist/leaflet.css" rel="stylesheet"/>
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
    <meta name="theme-color" content="#000000">

    <title>Leaflet map</title>
  </head>
  <body>
    <div id="root"></div>
  </body>
</html>


person RCohen    schedule 27.09.2018    source источник


Ответы (2)


Вам нужно будет отобразить несколько элементов Marker. В настоящее время вы визуализируете один Marker, если у вас есть текущее местоположение пользователей. Аналогичным образом мы пройдемся по другим координатам и визуализируем их.

В своем componentDidMount сохраните полученный ответ в состояние

fetch('https://api.citybik.es/v2/networks')
    .then(res => res.json())
    .then(response => {
        console.log("response", response);
        console.log(response.networks)
        const networkData = response.networks
        const networkList = networkData.map((data) => {
            this.setState({ bikeData: data });
        })
})

Затем внутри вашего метода render переберите bikeData и визуализируйте Markers.

<Map className="map" center={position} zoom={this.state.zoom}>
            <TileLayer
                attribution="&amp;copy <a href=&quot;http://osm.org/copyright&quot;>OpenStreetMap</a> contributors"
                url="https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png"
            />
            {
                this.state.haveUsersLocation ? 
                <Marker
                    position={position}
                    icon={myIcon}>
                <Popup>
                    A pretty CSS3 popup. <br /> Easily customizable.
                </Popup>
            </Marker> : ''
            }

            {
              this.state.bikeData &&
              this.state.bikeData.networks.map((idx, data) => (
               <Marker key={data.id} position={[data.location.latitude, data.location.longitude]}> <Popup> Name: {data.name} </Popup> </Marker>))
            } 

        </Map>
person Panther    schedule 27.09.2018
comment
Uncaught TypeError: this.state.bikeData.map is not a function @Пантера - person RCohen; 27.09.2018
comment
@RCohen, не следует напрямую копировать и вставлять ответ и пробовать его ... при переполнении стека мы просто даем указатели, а не точное решение. Попробуйте прочитать ошибку и посмотреть, что пошло не так, и исправить их. Проблема заключалась в том, что я пытался map использовать не то свойство. Я надеюсь, что this.state.bikeData.networks является массивом и будет повторять его. Пожалуйста, используйте stackoverflow для обучения и не ждите, что кто-то даст точное решение. Проверьте обновленный ответ. - person Panther; 27.09.2018
comment
Я только что упомянул, что ваш код не работает должным образом. У меня уже было что-то закодировано до того, как появился ваш ответ. Мы, как разработчики, здесь, чтобы делиться знаниями и помогать друг другу. В этом нет ничего плохого. @Пантера - person RCohen; 27.09.2018

Вам лучше переформатировать выходной JSON в GeoJSON, тогда будет довольно просто рисовать объект на карте с помощью Leaflet и Mapbox.

Пример:

https://gist.github.com/arfeo/3580f3796a31ca95b7cf30b99bfd6be5

    const openJsonFile = (file) => {
        return new Promise(function(resolve, reject) {
            const _ = new XMLHttpRequest();
            _.overrideMimeType("application/json");
            _.open("GET", file, true);
            _.onload = function() {
                if(this.readyState == 4) {
                    if(this.status >= 200 && this.status < 300) {
                        resolve(_.responseText);
                    } else {
                        reject(_.statusText);
                    }
                }
            };
            _.onerror = function () {
                reject(_.statusText);
            };
            _.send();
        });
    };

    // Create a map
    const map = L.map('map').setView([55.92, 37.83], 12);

    // Add Mapbox tiles to map
    L.tileLayer('https://api.tiles.mapbox.com/v4/{id}/{z}/{x}/{y}.png?access_token=pk.eyJ1IjoiYXJmZW8iLCJhIjoiY2pjeGw5bG5nMTF5ZjMzczZoZWVzbWdyNSJ9.Yh3u2uEHErWpTvAg3Ak_qw', {
        attribution: 'Map data &copy; <a href="http://openstreetmap.org">OpenStreetMap</a> contributors, <a href="http://creativecommons.org/licenses/by-sa/2.0/">CC-BY-SA</a>, Imagery © <a href="http://mapbox.com">Mapbox</a>',
        maxZoom: 20,
        id: 'mapbox.streets',
        accessToken: 'pk.eyJ1IjoiYXJmZW8iLCJhIjoiY2pjeGw5bG5nMTF5ZjMzczZoZWVzbWdyNSJ9.Yh3u2uEHErWpTvAg3Ak_qw'
    }).addTo(map);

    // Open and parse heojson file
    openJsonFile("output.geojson")
        .then(function(json) {
            const data = JSON.parse(json);

            // Draw GeoJSON features on map
            for(const feature of data.features) {
                const x = L.geoJSON(feature).addTo(map);

                // If a feature is Point, add a popup (if applicable)
                if(feature.geometry.type === "Point")
                    x.bindPopup((name = feature.properties.name) ? name : null);
            }
        })
        .catch(function(error) {
            console.error("Error:", error);
        });
person Arfeo    schedule 27.09.2018