calvinmetcalf / shapefile-js

Convert a Shapefile to GeoJSON. Not many caveats.
http://calvinmetcalf.github.io/shapefile-js/
714 stars 228 forks source link

Loading shapefile from URL #165

Closed sxwebster closed 2 years ago

sxwebster commented 2 years ago

The following example 3 different ways I've been trying to load a shefile from a URL.

I cannot use the method shown on the front page of the leaflet.shapefile repository as it errors our with L.shapefile is not a constructor. I'm struggling to use the examples found on the shapefile-js page because they result in trying to extract an arraybuffer from a returned Javascript Promise.

<!DOCTYPE html>
<html>

<head>

    <title>Load a shapefile into Leaflet from URL</title>

    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0">

    <link rel="stylesheet" href="https://unpkg.com/leaflet@1.7.1/dist/leaflet.css"
        integrity="sha512-xodZBNTC5n17Xt2atTPuE1HxjVMSvLVW9ocqUKLsCC5CXdbqCmblAshOMAS6/keqq/sMZMZ19scR4PsZChSR7A=="
        crossorigin="" />
    <script src="https://unpkg.com/leaflet@1.7.1/dist/leaflet.js"
        integrity="sha512-XQoYMqMTK8LvdxXYG3nZ448hOEQiglfqkJs1NOQV44cWnUrBc8PkAOcXy20w0vlaXaVUearIOBhiXZ5V3ynxwA=="
        crossorigin=""></script>
    <script src="https://unpkg.com/shpjs@latest/dist/shp.js" crossorigin=""></script>

</head>

<body>

    <div id="map" style="width: 600px; height: 400px;"></div>
    <script>

        async function get(url) {
            const response = await fetch(url).then(res => res.blob()).then(blob => blob.arrayBuffer());
            return response;
        }

        var myshp = get("https://tmp-demo-dev-bucket.s3.ap-southeast-2.amazonaws.com/my_boxes.zip")

        //----------------
        var watercolor = L.tileLayer('http://{s}.tile.osm.org/{z}/{x}/{y}.png', {

        });
        var geo = L.geoJson({ features: [] }, {
            onEachFeature: function popUp(f, l) {
                var out = [];
                if (f.properties) {
                    for (var key in f.properties) {
                        out.push(key + ": " + f.properties[key]);
                    }
                    l.bindPopup(out.join("<br />"));
                }
            }
        });
        var m = L.map('map', {
            center: [10, -1],
            zoom: 6,
            layers: [watercolor, geo]
        });

        m.setView(new L.LatLng(-27, 153), 6);

        //}}).addTo(m);

        //This is where we try to load the shapefile
        //It eventually needs to be added to the map
        console.log(myshp);
        myshplayer = shp(myshp).then(function(geojson){});
        myshplayer.addTo(m);
        console.log("Got to here ok");

        //alternatively tried this instead of the above rows
        shp(myshp).then(function (data) {
            geo.addData(data).catch(e=>console.log('error',e));
        });

        //Alternatively try the method listed on the front page of the repository. 
        new L.Shapefile(myshp); //This is a third method, it expects an arraybuffer, but this returns an error that it's not a valid constructor even though the libraries are loaded. 

        var baseMaps = {
            "BaseLayer": watercolor
        };
        var overlays = {
            "shapefile": geo
        };

        //L.control.layers(baseMaps, overlays).addTo(map);

    </script>

</body>

</html>
calvinmetcalf commented 2 years ago

ok so


        shp(myshp).then(function (data) {
            geo.addData(data).catch(e=>console.log('error',e));
        });

should be


        shp(url).then(function (data) {
            geo.addData(data)
        }).catch(e=>console.log('error',e));

that being said, what you were trying to do should have worked but there were a couple bugs

  1. get should be
 function get(url) {
            return fetch(url).then(res => res.arrayBuffer())
        }

or

 async function get(url) {
        const resp = await fetch(url);
        if (resp.status > 299) {
        throw new Error(resp.statusText);
       }
       return resp.arrayBuffer();

in other words you don't need to convert into a blob and then into an arrayBuffer

this returns a promise that needs to be resolved before it could be passed to shp

get("https://tmp-demo-dev-bucket.s3.ap-southeast-2.amazonaws.com/my_boxes.zip")
.then((myshape) =>shp(myshape))
.then((data) => {
   geo.addData(data)
}).catch(e=>console.log('error',e));

but again that isn't actually necessary since this library can take a url to a shapefile

lastly

 new L.Shapefile(myshp);

didn't work because:

  1. you are passing a promise for an array buffer instead of an array buffer
  2. you are loading the shapefile library but not the leaflet module for using it
sxwebster commented 2 years ago

Thank you very much for taking the time to reply. In the end the easiest way was to include the shapefile library. It's really good of you to illustrate the other way too, however.