Add Shapefile in JS 4.2

4638
10
02-14-2017 09:32 AM
BrandonPayne
New Contributor II

I'm wondering if anyone has been able to add a shapefile to a JS 4.2 app yet?  I see the documentation for doing it in 3.19 (Add shapefile | ArcGIS API for JavaScript 3.19 ) and I've gotten this to work as a local demo project, but there are a few changes from 3.x to 4.x that seem to make this quite hard to do.  Specifically, the esri/request has changed to no longer use a "form" parameter, so how do we upload the zipped shapefile now?  

Ideally, I'd like to be able to add an in-memory instance of a shapefile from a database download as well as a user selected shapefile from their local machine.  Anyone have some ideas on this? I'm starting to lean toward a third-party tool to parse out the geojson data from the shapefile and manually add in a new feature layer, but this seems like more work than should be necessary.

10 Replies
BrandonPayne
New Contributor II

For anyone else who comes upon this question I was finally able to solve it.  I used the same method as can be seen in the previous 'add shapefile' link to the 3.19 solution, but I pointed the 'generate' function to one that we have on our own servers.  This returns a featureCollection, which works great for 3.19 but must  be tweaked for a 4.x solution.  To do this, I loop through the featureCollection.layers and create a new FeatureLayer for each one as follows:

for (var i = 0; i < features.featureCollection.layers.length; i++) {
                            var lyr = features.featureCollection.layers[i];
                            var featLayer = new FeatureLayer({
                                fields: lyr.layerDefinition.fields,
                                objectIdField: lyr.layerDefinition.objectIdField,
                                geometryType: lyr.layerDefinition.geometryType,
                                spatialReference: SpatialReference.fromJSON(lyr.featureSet.spatialReference),
                                source: this.getFeatureObjects(lyr.featureSet.features),
                                visible: true,
                                renderer: rendererJsonUtils.fromJSON(lyr.layerDefinition.drawingInfo.renderer),
                            });
                            this.view.map.add(featLayer);
                        }


private getFeatureObjects(features): Collection {
        var featureObjs: any = [];
        for (var i = 0; i < features.length; i++) {
            var currFeat = features[i];
            var graphic = Graphic.fromJSON(currFeat);
            featureObjs.push(graphic);
        }
        return featureObjs;
    }‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍
BarbaraPutnam
New Contributor II

Were you able to upload a shapefile from your local machine in 4.x?  Generating features from a url works fine but since the form parameter is no longer supported, what did you use to specify the local file to upload?  Thanks

0 Kudos
BrandonPayne
New Contributor II

Hey Barbara,

I was able to upload the shapefile from my local machine in a .zip format.  Since the form parameter isn't supported using the 4.x API I just created a POST request with another technology, in this case Angular 2.  My functions to do this were as follows:

getFeaturesFromZip(zipFile, name, targetSR) {

        var form = new FormData();
        var publishParams = {
            'name': name,
            'targetSR': {
                'wkid': targetSR
            },
            'maxRecordCount': 1000,
            'enforceInputFileSizeLimit': true,
            'enforceOutputJsonSizeLimit': true
        };
        var extent = scaleUtils.getExtentForScale(this._configuration.map, 40000);
        var resolution = extent.getWidth() / this._configuration.map.width;
        publishParams.generalize = false;
        publishParams.maxAllowableOffset = resolution;
        publishParams.reducePrecision = false;
        publishParams.numberOfDigitsAfterDecimal = 0;
        form.append("publishParameters", JSON.stringify(publishParams));
        form.append("filetype", "shapefile");
        form.append("f", "json");
        let zipName = name = ".zip";
        form.append("file", this.dataURItoBlob(zipFile), zipName);
        return this.http.post(this._configuration.ShapefileServiceEndpoint, form)
            .map((res: Response) => res.json());
    }


private dataURItoBlob(dataURI) {
        var array = [];
        if (dataURI instanceof Array) {
            array = dataURI;
        }
        else {
            var binary = atob(dataURI);
            for (var i = 0; i < binary.length; i++) {
                array.push(binary.charCodeAt(i));
            }
        }
        return new Blob([new Uint8Array(array)], { type: 'application/zip' });
    }

Good luck!

BarbaraPutnam1
New Contributor II

Thanks for your response.  I have a couple of questions. You mentioned that you pointed the generate function to one you have on your server. I assume that's the  ShapefileServiceEndpoint in the http.post request in your code.  What does that function do and how do you host it so that the http request can post to it?  In our previous 3.x version we just used Esri's generate function but now we can't use it because FormData is not currently supported in the request object at 4.3. We are also using Angular with the Javascript api .   One other thing, you are referencing scaleUtils which it's my understanding that it isn't supported yet either in 4.x. 

Thanks for your help.

0 Kudos
BrandonPayne
New Contributor II

The generate function on our servers is the same as the one ESRI hosts, we just have an in-house ArcGIS server. The function is just part of the REST API so it just accepts a POST request by default. You could continue using the one hosted by ESRI without an issue as well. I was using the ESRI hosted generate method to test my local copy before moving it to our servers and was able to send it POST requests.

The 4.x JSAPI itself doesn't support the FormData option at the moment, however the server side doesn't really care about the JSAPI and still supports FormData. If you just use the built in Angular http.post function you should be able to get it to work. I'd recommend using something like Postman to test your REST calls out to the server - I've been using it and found it extremely useful for these cases.

I've modified my original function (which I had tweaked for 3.x - we've moved back down after running into too many issues with 4.x). The result is below.

getFeaturesFromZip(zipFile, name, targetSR) {

        var form = new FormData();
        form.append("publishParameters", "{\"name\":\"" + name + "\" ,\"targetSR\":{\"wkid\":" + targetSR + "},\"maxRecordCount\":1000,\"enforceInputFileSizeLimit\":true,\"enforceOutputJsonSizeLimit\":true,\"locationType\":\"none\",\"reducePrecision\":false,\"numberOfDigitsAfterDecimal\":1}");
        form.append("filetype", "shapefile");
        let zipName = name = ".zip";
        form.append("file", this.dataURItoBlob(zipFile), zipName);
        return this.http.post(this._configuration.ShapefileServiceEndpoint, form)
            .map((res: Response) => res.json());
    }
BarbaraPutnam1
New Contributor II

Thanks Brandon,

You were right about the http post. I was actually returning the data but I had some silly error in my parser that was hiding it. The Postman tool is great for testing and uncovering issues . Your featureCollection to  featureLayer function worked great too. Made it nice that I didn't have to convert the 3.x one myself. Thanks for including it and your responses. I really appreciate it.

0 Kudos
BrandonPayne
New Contributor II

Glad I could help. Good luck with the application!

0 Kudos
jaykapalczynski
Frequent Contributor

Do you have a JS Fiddle of this or something ... trying to follow and having a hard time.... I assume you click a button and then can navigate to the folder of the zip shapefile?

0 Kudos
jaykapalczynski
Frequent Contributor

Any help would be greatly appreciated

0 Kudos