Search code examples
javascriptfileinputgeojsonshapefile

Converting a shape file to a geo JSON file with JavaScript/TypeScript


I'm attempting to convert a shape zip file to a geoJSON file inside of my React app. I've come across a few packages that supposedly do such as ogr2ogr that but they all seem to require a file path in order to find the file. On top of that, all these packages i've been experimenting with all give me polyfill errors when compiling but that isn't the core issue here.

I'd like for the user to be able to select a file via and then convert that file. However, due to browser restrictions, I'm not able to get the full file path of the file that is input?

Does anyone know of a workaround or means to achieve what I'm trying to do?

My Basic code:

class App extends React.PureComponent {

  async viewFileData(files: any) {

    const file = files[0]
    
    // convert?
    
  }

  render() {
    return (
      <div className="App">
        <header className="App-header">
          <img src={logo} className="App-logo" alt="logo" />
          <p>
            Shape File Upload Test
          </p>
          {/* <button style={{width: 100, height: 50}} onClick={e => console.log("test")}>Upload</button> */}
          <input id="fileInput" type="file" multiple onChange={ (e) => {
            this.viewFileData(e.target.files)
          }}/>
        </header>
      </div>
    )
  }
}

export default App;

Solution

  • I haven't worked on this project in a while but If memory serves correct I ended up tweaking an answer I found on a another forum that used the shpjs package in order to solve my issue. Unfortunately, I have no idea where I what the forum was so apologies for not linking. The functions I used are below:

    import shp from "shpjs";
    
    /**
     * Parses & extracts geographic data from a zipped shape file
     * @param {FileList} files Files that we are extracting geo-spatial data from (zipped shapefile)
     * @param {Boolean} featureCollection Determines if we return a feature collection object or just the features themselves
     * @returns GeoJson object
     */
    const extractShapes = async (files, featureCollection=false) => {
      let result = {
        hasError: false,
        errorMessage: null,
        data: null
      };
    
      const _formatShape = (_data) => {
        return featureCollection ? _data : _data.features;
      };
    
      const _parseFile = async (_file) => {
        let _result = {
          hasError: false,
          errorMessage: null,
          data: null
        };
    
        let _data = await _file
          .arrayBuffer()
          .then((_buffer) => shp(_buffer))
          .catch((_err) => {
            console.error(_err);
            _result.hasError = true;
            _result.errorMessage = "IMPORT_UNRECOGNISED_FILE";
            return null;
          });
    
        _result.data = _formatShape(_data);
    
        if (_result.hasError) return _result;
    
        if (!_result.data || _result.data.length < 1) {
          _result.hasError = true;
          _result.errorMessage = "EXTRACT_FILE_EMPTY";
        }
    
        return _result;
      };
    
      // read the files
      result.data = await Promise.all(
        Array.prototype.map.call(files, _parseFile)
      ).catch((err) => {
        console.error(err);
        result.hasError = true;
        result.errorMessage = "Extract went wrong";
        return null;
      });
    
      if (result.hasError) return result;
    
      if (!result.data || result.data.length < 1) {
        result.hasError = true;
        result.errorMessage = "IMPORT_SHAPE_EMPTY";
      }
    
      return result.data[0].data;
    };
    
    export { extractShapes };