/* (c) 2015 - 2016 Open Source Geospatial Foundation - all rights reserved * This code is licensed under the GPL 2.0 license, available at the root * application directory. */ package org.geoserver.wms.utfgrid; import java.awt.image.BufferedImage; import java.awt.image.Raster; import java.awt.image.RenderedImage; import java.io.PrintWriter; import java.util.ArrayList; import java.util.Calendar; import java.util.Iterator; import java.util.List; import java.util.Map; import org.geoserver.wms.map.RawMap; import org.geoserver.wms.utfgrid.UTFGridEntries.UTFGridEntry; import org.geotools.util.Converters; import org.geotools.xml.gml.GMLComplexTypes.GeometryPropertyType; import org.opengis.feature.Feature; import org.opengis.feature.Property; import org.opengis.feature.simple.SimpleFeature; import org.opengis.feature.type.AttributeDescriptor; import org.opengis.feature.type.GeometryDescriptor; import net.sf.json.util.JSONBuilder; import net.sf.json.util.JSONStringer; public class UTFGridMap extends RawMap { private RenderedImage image; public UTFGridMap(final UTFGridMapContent mapContent, RenderedImage image) { super(mapContent, (byte[]) null, UTFGridMapOutputFormat.MIME_TYPE); this.image = image; } public void writeTo(java.io.OutputStream out) throws java.io.IOException { UTFGridEntries entries = getEntries(); PrintWriter pw = new PrintWriter(out); pw.println("{"); pw.println("\"grid\": ["); List<UTFGridEntry> encodedEntries = writeGrid(pw, image, entries); pw.println("],"); pw.println("\"keys\": ["); if(encodedEntries.isEmpty()) { pw.println(" \"\""); } else { pw.println(" \"\","); for (Iterator<UTFGridEntry> it = encodedEntries.iterator(); it.hasNext();) { UTFGridEntry entry = (UTFGridEntry) it.next(); pw.print(" \""); pw.print(entry.getKey()); if (it.hasNext()) { pw.println("\","); } else { pw.println("\""); } } } pw.println("],"); pw.println("\"data\": {"); for (Iterator<UTFGridEntry> it = encodedEntries.iterator(); it.hasNext();) { UTFGridEntry entry = (UTFGridEntry) it.next(); pw.print(" \""); pw.print(entry.getKey()); pw.print("\" : "); pw.print(getAttributesJson(entry.getFeature())); if (it.hasNext()) { pw.println(","); } } pw.println("}"); pw.println("}"); pw.flush(); } private String getAttributesJson(Feature feature) { JSONBuilder builder = new JSONStringer().object(); builder.key("id").value(feature.getIdentifier().toString()); if (feature instanceof SimpleFeature) { SimpleFeature sf = (SimpleFeature) feature; for (AttributeDescriptor ad : sf.getFeatureType().getAttributeDescriptors()) { if (ad instanceof GeometryDescriptor) { continue; } else { String name = ad.getLocalName(); Object value = sf.getAttribute(name); addAttribute(builder, name, value); } } } else { for (Property p : feature.getProperties()) { if (p.getType() instanceof GeometryPropertyType) { continue; } String name = p.getName().getLocalPart(); Object value = p.getValue(); addAttribute(builder, name, value); } } builder.endObject(); return builder.toString(); } private void addAttribute(JSONBuilder builder, String name, Object value) { if (value instanceof java.util.Date || value instanceof Calendar) { value = Converters.convert(value, String.class); } builder.key(name).value(value); } /** * Writes the grid, and maps the original values into a compact sequence of keys (the original values might be sparse due to features being fully * overwritten by other features) * * @param pw * @param image * @param entries * */ private List<UTFGridEntry> writeGrid(PrintWriter pw, RenderedImage image, UTFGridEntries entries) { Map<Integer, UTFGridEntry> keyToFeature = entries.getEntryMap(); List<UTFGridEntry> result = new ArrayList<UTFGridEntry>(); int key = 1; Raster data = getData(image); int width = data.getWidth(); int[] pixels = new int[width]; int height = data.getHeight(); for (int r = 0; r < height; r++) { data.getDataElements(0, r, width, 1, pixels); pw.print("\""); for (int i = 0; i < pixels.length; i++) { int pixel = pixels[i] & 0xFFFFFF; if (pixel == 0) { pw.print(" "); } else { UTFGridEntry entry = keyToFeature.get(pixel); if (entry == null) { throw new RuntimeException("Could not find entry for pixel value " + pixel + ". This normally means there is some color altering option at work " + "that the UTFGrid code failed to remove, like opacity, blending and the like"); } int entryKey = entry.getKey(); if (entryKey == -1) { entryKey = key++; entry.setKey(entryKey); result.add(entry); } pw.print(getGridChar(entryKey)); } } if (r < height - 1) { pw.println("\","); } else { pw.println("\""); } } return result; } private Raster getData(RenderedImage image) { if (image instanceof BufferedImage) { // copy-less version of data access return ((BufferedImage) image).getRaster(); } else { return image.getData(); } } /** * From the spec, the encoding works as follows: * <ul> * <li>Add 32.</li> * <li>If the result is >= 34, add 1.</li> * <li>If the result is >= 92, add 1.</li> * </ul> */ private char getGridChar(int val) { int result = val + 32; if (result >= 34) { result++; } if (result >= 92) { result++; } return (char) result; } UTFGridEntries getEntries() { UTFGridMapContent mc = (UTFGridMapContent) mapContent; return mc.getEntries(); } }