code: /*******************************************************************************
   * Copyright (c) 2015 VoyagerSearch and others
   * All rights reserved. This program and the accompanying materials
   * are made available under the terms of the Apache License, Version 2.0 which
   * accompanies this distribution and is available at
   *    http://www.apache.org/licenses/LICENSE-2.0.txt
   ******************************************************************************/
  
  package org.locationtech.spatial4j.io.jackson;
  
  import java.io.IOException;
  import java.util.Iterator;
  
  import org.locationtech.spatial4j.distance.DistanceUtils;
  import org.locationtech.spatial4j.shape.Circle;
  import org.locationtech.spatial4j.shape.Point;
  import org.locationtech.spatial4j.shape.Rectangle;
  import org.locationtech.spatial4j.shape.Shape;
  import org.locationtech.spatial4j.shape.ShapeCollection;
  import org.locationtech.spatial4j.shape.impl.BufferedLine;
  import org.locationtech.spatial4j.shape.impl.BufferedLineString;
  import org.locationtech.spatial4j.shape.impl.GeoCircle;
  import org.locationtech.spatial4j.shape.jts.JtsGeometry;
  
  import com.fasterxml.jackson.core.JsonGenerator;
  import com.fasterxml.jackson.core.JsonProcessingException;
  import com.fasterxml.jackson.databind.JsonSerializer;
  import com.fasterxml.jackson.databind.SerializerProvider;
  
  public class ShapeAsGeoJSONSerializer extends JsonSerializer<Shape>
  {
    static final String BUFFER = "buffer";
    static final String BUFFER_UNITS = "buffer_units";
    
    final GeometryAsGeoJSONSerializer forJTS = new GeometryAsGeoJSONSerializer();
    
  
    protected void write(JsonGenerator gen, double... coords) throws IOException {
      gen.writeStartArray();
      for (double coord : coords) {
        if (Double.isNaN(coord)) {
          break; // empty
        }
        gen.writeNumber(coord);
      }
      gen.writeEndArray();
    }
  
    
    // Keep 6 decimal places
    public static double round(double v) {
      return Math.round(v * 1000000d) / 1000000d;
    }
    
    /**
     * Helper method to encode a distance property (with optional unit). 
     * <p>
     *  The distance unit is only encoded when <tt>isGeo</tt> is true, and it is converted to km. 
     * </p>
     * <p>
     *  The distance unit is encoded within a properties object.
     * </p>
     * @param output The writer.
     * @param nf The number format.
     * @param dist The distance value to encode.
     * @param isGeo The flag determining 
     * @param distProperty The distance property name.
     * @param distUnitsProperty The distance unit property name.
     */
    void writeDistance(JsonGenerator gen, double dist, boolean isGeo, String distProperty, String distUnitsProperty) 
        throws IOException {
      gen.writeFieldName(distProperty);
      if (isGeo) {
        double distKm = DistanceUtils.degrees2Dist(dist, DistanceUtils.EARTH_MEAN_RADIUS_KM);
        gen.writeNumber( round(distKm) );
        gen.writeFieldName("properties");
        gen.writeStartObject();
        gen.writeFieldName(distUnitsProperty);
        gen.writeString("km");
        gen.writeEndObject();
      } else {
        gen.writeNumber(dist);
      }
    }
  
    public void write(JsonGenerator gen, Shape shape) throws IOException {
      if (shape == null) {
        throw new NullPointerException("Shape can not be null");
      }
      if (shape instanceof Point) {
        Point v = (Point) shape;
        gen.writeStartObject();
        gen.writeFieldName("type");
        gen.writeString("Point");
        gen.writeFieldName("coordinates");
        write(gen, v.getX(), v.getY());
        gen.writeEndObject();
        return;
      }
      if (shape instanceof Rectangle) {
        Rectangle v = (Rectangle) shape;
        gen.writeStartObject();
        gen.writeFieldName("type");
        gen.writeString("Polygon");
        gen.writeFieldName("coordinates");
        gen.writeStartArray();
        gen.writeStartArray();
        write(gen, v.getMinX(), v.getMinY());
        write(gen, v.getMinX(), v.getMaxY());
        write(gen, v.getMaxX(), v.getMaxY());
        write(gen, v.getMaxX(), v.getMinY());
        write(gen, v.getMinX(), v.getMinY());
        gen.writeEndArray();
        gen.writeEndArray();
        gen.writeEndObject();
        return;
      }
      if (shape instanceof BufferedLine) {
        BufferedLine v = (BufferedLine) shape;
        gen.writeStartObject();
        gen.writeFieldName("type");
        gen.writeString("LineString");
        gen.writeFieldName("coordinates");
        gen.writeStartArray();
        write(gen, v.getA().getX(), v.getA().getY());
        write(gen, v.getB().getX(), v.getB().getY());
        gen.writeEndArray();
        if (v.getBuf() > 0) {
          writeDistance(gen, v.getBuf(), shape.getContext().isGeo(), BUFFER, BUFFER_UNITS);
        }
        gen.writeEndObject();
        return;
      }
      if (shape instanceof BufferedLineString) {
        BufferedLineString v = (BufferedLineString) shape;
        gen.writeStartObject();
        gen.writeFieldName("type");
        gen.writeString("LineString");
        gen.writeFieldName("coordinates");
        gen.writeStartArray();
        BufferedLine last = null;
        Iterator<BufferedLine> iter = v.getSegments().iterator();
        while (iter.hasNext()) {
          BufferedLine seg = iter.next();
          write(gen, seg.getA().getX(), seg.getA().getY());
          last = seg;
        }
        if (last != null) {
          write(gen, last.getB().getX(), last.getB().getY());
        }
        gen.writeEndArray();
        if (v.getBuf() > 0) {
          writeDistance(gen, v.getBuf(), shape.getContext().isGeo(), BUFFER, BUFFER_UNITS);
        }
        gen.writeEndObject();
        return;
      }
      if (shape instanceof Circle) {
        // See: https://github.com/geojson/geojson-spec/wiki/Proposal---Circles-and-Ellipses-Geoms
        Circle v = (Circle) shape;
        Point center = v.getCenter();
        gen.writeStartObject();
        gen.writeFieldName("type");
        gen.writeString("Circle");
        gen.writeFieldName("coordinates");
        write(gen, center.getX(), center.getY());
        writeDistance(gen, v.getRadius(), v instanceof GeoCircle, "radius", "radius_units");
        gen.writeEndObject();
        return;
      }
      if (shape instanceof ShapeCollection) {
        ShapeCollection v = (ShapeCollection) shape;
        gen.writeStartObject();
        gen.writeFieldName("type");
        gen.writeString("GeometryCollection");
        gen.writeFieldName("geometries");
        gen.writeStartArray();
        for (int i = 0; i < v.size(); i++) {
          write(gen, v.get(i));
        }
        gen.writeEndArray();
        gen.writeEndObject();
        return;
      }
  
      // Write the unknown geometry to WKT
      gen.writeStartObject();
      gen.writeFieldName("type");
      gen.writeString("Unknown");
      gen.writeFieldName("wkt");
      gen.writeString(shape.getContext().getFormats().getWktWriter().toString(shape));
      gen.writeEndObject();
    }
    
    
    @Override
    public void serialize(Shape shape, JsonGenerator gen,
        SerializerProvider serializers) throws IOException,
        JsonProcessingException {
  
      // Use the specialized class
      if (shape instanceof JtsGeometry) {
        forJTS.serialize(((JtsGeometry) shape).getGeom(), gen, serializers);
        return;
      }
      
      write(gen, shape);
    }
  }