/*
* GeoTools - The Open Source Java GIS Toolkit
* http://geotools.org
*
* (C) 2017, Open Source Geospatial Foundation (OSGeo)
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation;
* version 2.1 of the License.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
*/
package org.geotools.mbstyle.layer;
import org.geotools.mbstyle.MBStyle;
import org.geotools.mbstyle.parse.MBFilter;
import org.geotools.mbstyle.parse.MBFormatException;
import org.geotools.mbstyle.parse.MBObjectParser;
import org.geotools.mbstyle.transform.MBStyleTransformer;
import org.geotools.styling.*;
import org.geotools.text.Text;
import org.json.simple.JSONObject;
import org.opengis.filter.expression.Expression;
import org.opengis.style.GraphicFill;
import org.opengis.style.SemanticType;
import javax.measure.unit.NonSI;
import java.awt.*;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
/**
* MBLayer wrapper for "fill extrusion" representing extruded (3D) polygon.
* <p>
* Example of line JSON:<pre>
* { 'id': 'room-extrusion',
* 'type': 'fill-extrusion',
* 'source': {
* 'type': 'geojson',
* 'data': 'https://www.mapbox.com/mapbox-gl-js/assets/data/indoor-3d-map.geojson'
* },
* 'paint': {
* 'fill-extrusion-color': { 'property': 'color', 'type': 'identity'},
* 'fill-extrusion-height': { 'property': 'height','type': 'identity'},
* 'fill-extrusion-base': { 'property': 'base_height','type': 'identity'},
* 'fill-extrusion-opacity': 0.5
* }
* }
* </pre>
*
* Responsible for accessing wrapped json as expressions (for use in transformer).
*
* @author jody
*/
public class FillExtrusionMBLayer extends MBLayer {
private JSONObject paint;
private JSONObject layout;
private static String TYPE = "fill-extrusion";
public enum TranslateAnchor {
/**
* Translation relative to the map.
*/
MAP,
/**
* Translation relative to the viewport.
*/
VIEWPORT
}
public FillExtrusionMBLayer(JSONObject json) {
super(json,new MBObjectParser(FillExtrusionMBLayer.class));
paint = paint();
layout = layout();
}
@Override
protected SemanticType defaultSemanticType() {
return SemanticType.POLYGON;
}
/**
* (Optional) Defaults to 1.
*
* The opacity of the entire fill extrusion layer. This is rendered on a per-layer, not per-feature, basis, and
* data-driven styling is not available.
*
* @return The opacity of the fill extrusion layer.
* @throws MBFormatException
*/
public Number getFillExtrusionOpacity() throws MBFormatException {
return parse.optional(Double.class, paint, "fill-extrusion-opacity", 1.0);
}
/**
* Access fill-extrusion-opacity as literal or function expression
*
* @return The opacity of the fill extrusion layer.
* @throws MBFormatException
*/
public Expression fillExtrusionOpacity() throws MBFormatException {
return parse.percentage(paint, "fill-extrusion-opacity", 1.0);
}
/**
* (Optional). Defaults to #000000. Disabled by fill-extrusion-pattern.
*
* The base color of the extruded fill. The extrusion's surfaces will be shaded differently based on this
* color in combination with the root light settings.
*
* If this color is specified as rgba with an alpha component, the alpha component will be ignored; use
* fill-extrusion-opacity to set layer opacity.
*
* @return The color of the extruded fill.
*/
public Color getFillExtrusionColor() throws MBFormatException {
return parse.optional(Color.class, paint, "fill-extrusion-color", Color.BLACK );
}
/**
* Access fill-extrusion-color as literal or function expression, defaults to black.
*
* @return The color of the extruded fill.
*/
public Expression fillExtrusionColor() throws MBFormatException {
return parse.color(paint, "fill-extrusion-color", Color.BLACK);
}
/**
* (Optional) Units in pixels. Defaults to 0,0.
*
* The geometry's offset. Values are [x, y] where negatives indicate left and up (on the flat plane), respectively.
*
* @return The geometry's offset, in pixels.
*/
public int[] getFillExtrusionTranslate() throws MBFormatException{
return parse.array( paint, "fill-extrusion-translate", new int[]{ 0, 0 } );
}
/**
* Access fill-extrusion-translate as Point
*
* @return The geometry's offset, in pixels.
*/
public Point fillExtrusionTranslate() {
int[] translate = getFillExtrusionTranslate();
return new Point(translate[0], translate[1]);
}
/**
* (Optional) One of map, viewport. Defaults to map. Requires fill-extrusion-translate.
*
* Controls the translation reference point.
*
* {@link TranslateAnchor#MAP}: The fill extrusion is translated relative to the map.
*
* {@link TranslateAnchor#VIEWPORT}: The fill extrusion is translated relative to the viewport.
*
* Defaults to {@link TranslateAnchor#MAP}.
*
* @return The translation reference point
*/
public TranslateAnchor getFillExtrusionTranslateAnchor() {
Object value = paint.get("fill-extrusion-translate-anchor");
if (value != null && "viewport".equalsIgnoreCase((String) value)) {
return TranslateAnchor.VIEWPORT;
} else {
return TranslateAnchor.MAP;
}
}
/**
* (Optional) Name of image in sprite to use for drawing images on extruded fills. For seamless patterns, image
* width and height must be a factor of two (2, 4, 8, ..., 512).
*
* @return The name of the image sprite, or null if not defined.
*/
public Expression getFillExtrusionPattern() throws MBFormatException{
return parse.string(paint, "fill-extrusion-pattern", null);
}
/**
* (Optional) Units in meters. Defaults to 0. The height with which to extrude this layer.
*
* @return The height with which to extrude this layer.
*/
public Number getFillExtrusionHeight() throws MBFormatException {
return parse.optional(Double.class, paint, "fill-extrusion-height", 0.0);
}
/**
* Access fill-extrusion-height as literal or function expression
*
* @return The height with which to extrude this layer.
* @throws MBFormatException
*/
public Expression fillExtrusionHeight() throws MBFormatException {
return parse.percentage(paint, "fill-extrusion-height", 0.0);
}
/**
* (Optional) Units in meters. Defaults to 0. Requires fill-extrusion-height.
*
* The height with which to extrude the base of this layer. Must be less than or equal to fill-extrusion-height.
*
* @return The height with which to extrude the base of this layer
*/
public Number getFillExtrusionBase() throws MBFormatException {
return parse.optional(Double.class, paint, "fill-extrusion-base", 0.0);
}
/**
* Access fill-extrusion-base as literal or function expression
*
* @return The height with which to extrude the base of this layer
* @throws MBFormatException
*/
public Expression fillExtrusionBase() throws MBFormatException {
return parse.percentage(paint, "fill-extrusion-base", 0.0);
}
/**
* Transform {@link FillExtrusionMBLayer} to GeoTools FeatureTypeStyle.
*
* @param styleContext The MBStyle to which this layer belongs, used as a context for things like resolving sprite and glyph names to full urls.
*/
public List<FeatureTypeStyle> transformInternal(MBStyle styleContext) {
PolygonSymbolizer symbolizer;
MBStyleTransformer transformer = new MBStyleTransformer(parse);
// from fill pattern or fill color
Fill fill;
DisplacementImpl displacement = new DisplacementImpl();
displacement.setDisplacementX(getFillExtrusionBase().doubleValue());
displacement.setDisplacementY(getFillExtrusionHeight().doubleValue());
if (getFillExtrusionPattern() != null) {
//Fill graphic (with external graphics)
ExternalGraphic eg = transformer.createExternalGraphicForSprite(getFillExtrusionPattern(), styleContext);
GraphicFill gf = sf.graphicFill(Arrays.asList(eg), fillExtrusionOpacity(), null, null, null, displacement);
fill = sf.fill(gf, null, null);
} else {
fill = sf.fill(null, fillExtrusionColor(), fillExtrusionOpacity());
}
symbolizer = sf.polygonSymbolizer(
getId(),
ff.property((String)null),
sf.description(Text.text("fill"),null),
NonSI.PIXEL,
null,
fill,
displacement,
ff.literal(0));
MBFilter filter = getFilter();
Rule rule = sf.rule(
getId(),
null,
null,
0.0,
Double.POSITIVE_INFINITY,
Arrays.asList(symbolizer),
filter.filter());
// Set legend graphic to null.
//setLegend(null) to empty list.
rule.setLegendGraphic(new Graphic[0]);
return Collections.singletonList(sf.featureTypeStyle(
getId(),
sf.description(
Text.text("MBStyle "+getId()),
Text.text("Generated for "+getSourceLayer())),
null, // (unused)
Collections.emptySet(),
filter.semanticTypeIdentifiers(),
Arrays.asList(rule)
));
}
/**
* Rendering type of this layer.
*
* @return {@link #TYPE}
*/
@Override
public String getType() {
return TYPE;
}
}