001package edu.wisc.ssec.mcidasv;
002
003import ucar.unidata.util.FileManager;
004
005import javax.swing.*;
006import javax.swing.filechooser.FileNameExtensionFilter;
007import java.io.*;
008import java.nio.*;
009import java.util.*;
010import java.util.logging.Logger;
011
012public class ShapefileToGeoJSON {
013    private static final Logger logger = Logger.getLogger(ShapefileToGeoJSON.class.getName());
014
015    public static boolean convert(String inputPath, String outputPath) {
016        String dbfPath = inputPath.substring(0, inputPath.lastIndexOf(".")) + ".dbf";
017        File shpFile = new File(inputPath);
018        File dbfFile = new File(dbfPath);
019
020        if (!shpFile.exists()) return false;
021
022        try (DataInputStream shpStream = new DataInputStream(new FileInputStream(shpFile));
023             FileWriter writer = new FileWriter(outputPath)) {
024
025            shpStream.skipBytes(100);
026
027            List<Map<String, Object>> attributes = dbfFile.exists() ? parseDbf(dbfFile) : new ArrayList<>();
028
029            writer.write("{\"type\": \"FeatureCollection\", \"features\": [");
030            int recordIdx = 0;
031            boolean firstFeature = true;
032
033            while (shpStream.available() > 0) {
034                int length = shpStream.readInt() * 2;
035                byte[] data = new byte[length];
036                shpStream.readFully(data);
037                ByteBuffer bb = ByteBuffer.wrap(data).order(ByteOrder.LITTLE_ENDIAN);
038
039                int type = bb.getInt(0);
040                if (type == 3 || type == 5) {
041                    if (!firstFeature) writer.write(",");
042                    firstFeature = false;
043
044                    boolean isPolygon = (type == 5);
045                    int numParts = bb.getInt(36);
046                    int numPoints = bb.getInt(40);
047                    int coordStart = 44 + (numParts * 4);
048
049                    int[] parts = new int[numParts];
050                    for (int p = 0; p < numParts; p++) {
051                        parts[p] = bb.getInt(44 + p * 4);
052                    }
053
054                    if (isPolygon) {
055                        boolean isMulti = numParts > 1;
056                        String geomType = isMulti ? "MultiPolygon" : "Polygon";
057                        writer.write("{\"type\": \"Feature\", \"geometry\": {\"type\": \"" + geomType + "\", \"coordinates\": [");
058
059                        for (int p = 0; p < numParts; p++) {
060                            if (p > 0) writer.write(",");
061                            int start = parts[p];
062                            int end = (p + 1 < numParts) ? parts[p + 1] : numPoints;
063
064                            if (isMulti) writer.write("[");
065                            writer.write("[");
066                            for (int i = start; i < end; i++) {
067                                double lon = bb.getDouble(coordStart + (i * 16));
068                                double lat = bb.getDouble(coordStart + (i * 16) + 8);
069                                if (i > start) writer.write(",");
070                                writer.write("[" + lon + "," + lat + "]");
071                            }
072                            writer.write("]");
073                            if (isMulti) writer.write("]");
074                        }
075                    } else {
076                        writer.write("{\"type\": \"Feature\", \"geometry\": {\"type\": \"LineString\", \"coordinates\": [");
077                        for (int i = 0; i < numPoints; i++) {
078                            double lon = bb.getDouble(coordStart + (i * 16));
079                            double lat = bb.getDouble(coordStart + (i * 16) + 8);
080                            if (i > 0) writer.write(",");
081                            writer.write("[" + lon + "," + lat + "]");
082                        }
083                    }
084
085                    writer.write("]}, \"properties\": ");
086                    if (recordIdx < attributes.size()) {
087                        writer.write(mapToJson(attributes.get(recordIdx)));
088                    } else {
089                        writer.write("{}");
090                    }
091                    writer.write("}");
092                }
093                recordIdx++;
094            }
095            writer.write("]}");
096            return true;
097        } catch (Exception e) {
098            e.printStackTrace();
099            return false;
100        }
101    }
102
103    private static List<Map<String, Object>> parseDbf(File file) throws Exception {
104        List<Map<String, Object>> rows = new ArrayList<>();
105        try (FileInputStream fis = new FileInputStream(file)) {
106            byte[] header = new byte[32];
107            fis.read(header);
108            ByteBuffer hb = ByteBuffer.wrap(header).order(ByteOrder.LITTLE_ENDIAN);
109            int numRecords = hb.getInt(4);
110            short headerLength = hb.getShort(8);
111            short recordLength = hb.getShort(10);
112
113            List<String> fieldNames = new ArrayList<>();
114            List<Integer> fieldWidths = new ArrayList<>();
115
116            int bytesRead = 32;
117            while (bytesRead < headerLength - 1) {
118                byte[] fieldBuf = new byte[32];
119                fis.read(fieldBuf);
120                fieldNames.add(new String(fieldBuf, 0, 11).trim());
121                fieldWidths.add(fieldBuf[16] & 0xFF);
122                bytesRead += 32;
123            }
124            fis.skip(1);
125
126            for (int i = 0; i < numRecords; i++) {
127                byte[] recBuf = new byte[recordLength];
128                fis.read(recBuf);
129                Map<String, Object> row = new LinkedHashMap<>();
130                int offset = 1;
131                for (int f = 0; f < fieldNames.size(); f++) {
132                    int width = fieldWidths.get(f);
133                    row.put(fieldNames.get(f), new String(recBuf, offset, width).trim());
134                    offset += width;
135                }
136                rows.add(row);
137            }
138        }
139        return rows;
140    }
141
142    private static String mapToJson(Map<String, Object> map) {
143        StringBuilder sb = new StringBuilder("{");
144        map.forEach((k, v) -> sb.append("\"").append(k).append("\":\"").append(v).append("\","));
145        if (sb.length() > 1) sb.setLength(sb.length() - 1);
146        return sb.append("}").toString();
147    }
148
149    public static String getShapeFile() {
150        FileNameExtensionFilter filter = new FileNameExtensionFilter("Shape Files (*.shp)", "shp");
151        return FileManager.getReadFile("Load File", filter);
152    }
153}