package com.revolsys.swing.map.layer.record.style;
import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Graphics2D;
import java.awt.Shape;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.TreeMap;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import javax.measure.Measure;
import javax.measure.quantity.Length;
import javax.measure.unit.NonSI;
import javax.measure.unit.Unit;
import javax.swing.Icon;
import com.revolsys.awt.WebColors;
import com.revolsys.collection.map.LinkedHashMapEx;
import com.revolsys.collection.map.MapEx;
import com.revolsys.datatype.DataType;
import com.revolsys.io.map.MapSerializer;
import com.revolsys.logging.Logs;
import com.revolsys.properties.BaseObjectWithPropertiesAndChange;
import com.revolsys.spring.resource.Resource;
import com.revolsys.swing.map.Viewport2D;
import com.revolsys.swing.map.layer.record.style.marker.AbstractMarker;
import com.revolsys.swing.map.layer.record.style.marker.ImageMarker;
import com.revolsys.swing.map.layer.record.style.marker.Marker;
import com.revolsys.swing.map.layer.record.style.marker.ShapeMarker;
import com.revolsys.swing.map.symbol.SymbolLibrary;
import com.revolsys.util.Property;
import com.revolsys.util.Strings;
public class MarkerStyle extends BaseObjectWithPropertiesAndChange
implements Cloneable, MapSerializer {
private static final Map<String, Object> DEFAULT_VALUES = new TreeMap<>();
public static final AbstractMarker ELLIPSE = new ShapeMarker("ellipse");
public static final Measure<Length> ONE_PIXEL = Measure.valueOf(1, NonSI.PIXEL);
private static final Set<String> PROPERTY_NAMES = new HashSet<>();
public static final Measure<Length> TEN_PIXELS = Measure.valueOf(10, NonSI.PIXEL);
public static final Measure<Length> ZERO_PIXEL = Measure.valueOf(0, NonSI.PIXEL);
static {
addStyleProperty("marker", null);
addStyleProperty("markerAllowOverlap", false);
addStyleProperty("markerClip", true);
addStyleProperty("markerCompOp", null);
addStyleProperty("markerDx", ZERO_PIXEL);
addStyleProperty("markerDy", ZERO_PIXEL);
addStyleProperty("markerFile", null);
addStyleProperty("markerFill", new Color(0, 0, 255));
addStyleProperty("markerFillOpacity", 255);
addStyleProperty("markerHeight", TEN_PIXELS);
addStyleProperty("markerHorizontalAlignment", "center");
addStyleProperty("markerIgnorePlacement", null);
addStyleProperty("markerLineColor", new Color(255, 255, 255));
addStyleProperty("markerLineOpacity", 255);
addStyleProperty("markerLineWidth", ONE_PIXEL);
addStyleProperty("markerOpacity", 255);
addStyleProperty("markerOrientation", 0.0);
addStyleProperty("markerOrientationType", "auto");
addStyleProperty("markerPlacementType", "auto");
addStyleProperty("markerSmooth", 0.0);
addStyleProperty("markerTransform", null);
addStyleProperty("markerType", "ellipse");
addStyleProperty("markerVerticalAlignment", "middle");
addStyleProperty("markerWidth", TEN_PIXELS);
}
protected static final void addStyleProperty(final String name, final Object defaultValue) {
PROPERTY_NAMES.add(name);
if (defaultValue != null) {
DEFAULT_VALUES.put(name, defaultValue);
}
}
public static <T> T getWithDefault(final T value, final T defaultValue) {
if (value == null) {
return defaultValue;
} else {
return value;
}
}
public static MarkerStyle marker(final AbstractMarker marker, final double markerSize,
final Color lineColor, final Color fillColor) {
final MarkerStyle style = new MarkerStyle();
style.setMarker(marker, markerSize, lineColor, fillColor);
return style;
}
public static MarkerStyle marker(final Shape shape, final double markerSize,
final Color lineColor, final double lineWidth, final Color fillColor) {
final AbstractMarker marker = new ShapeMarker(shape);
return marker(marker, markerSize, lineColor, fillColor);
}
public static MarkerStyle marker(final String markerName, final double markerSize,
final Color lineColor, final double lineWidth, final Color fillColor) {
final AbstractMarker marker = new ShapeMarker(markerName);
final MarkerStyle style = marker(marker, markerSize, lineColor, fillColor);
style.setMarkerLineWidth(Measure.valueOf(lineWidth, NonSI.PIXEL));
return style;
}
private Marker marker = ELLIPSE;
private boolean markerAllowOverlap;
private boolean markerClip = true;
private String markerCompOp;
private Measure<Length> markerDx = ZERO_PIXEL;
private Measure<Length> markerDy = ZERO_PIXEL;
private String markerFile;
private Resource markerFileResource;
private Color markerFill = new Color(0, 0, 255, 255);
private int markerFillOpacity = 255;
private Measure<Length> markerHeight = TEN_PIXELS;
private String markerHorizontalAlignment = "center";
private String markerIgnorePlacement;
private Color markerLineColor = new Color(255, 255, 255, 255);
private int markerLineOpacity = 255;
private Measure<Length> markerLineWidth = ONE_PIXEL;
private int markerOpacity = 255;
/** The orientation of the text in a clockwise direction from the east axis. */
private double markerOrientation = 0;
private String markerOrientationType = "auto";
private String markerPlacementType = "auto";
private double markerSmooth = 0;
private String markerTransform;
private String markerType = "ellipse";
private String markerVerticalAlignment = "middle";
private Measure<Length> markerWidth = TEN_PIXELS;
public MarkerStyle() {
}
public MarkerStyle(final Map<String, Object> style) {
setProperties(style);
}
@Override
public MarkerStyle clone() {
return (MarkerStyle)super.clone();
}
public Marker getMarker() {
return this.marker;
}
public String getMarkerCompOp() {
return this.markerCompOp;
}
public Measure<Length> getMarkerDx() {
return this.markerDx;
}
public Measure<Length> getMarkerDy() {
return this.markerDy;
}
public String getMarkerFile() {
return this.markerFile;
}
public Color getMarkerFill() {
return this.markerFill;
}
public int getMarkerFillOpacity() {
return this.markerFillOpacity;
}
public Measure<Length> getMarkerHeight() {
return this.markerHeight;
}
public String getMarkerHorizontalAlignment() {
return this.markerHorizontalAlignment;
}
public String getMarkerIgnorePlacement() {
return this.markerIgnorePlacement;
}
public Color getMarkerLineColor() {
return this.markerLineColor;
}
public int getMarkerLineOpacity() {
return this.markerLineOpacity;
}
public Measure<Length> getMarkerLineWidth() {
return this.markerLineWidth;
}
public int getMarkerOpacity() {
return this.markerOpacity;
}
public double getMarkerOrientation() {
return this.markerOrientation;
}
public String getMarkerOrientationType() {
return this.markerOrientationType;
}
public String getMarkerPlacementType() {
return this.markerPlacementType;
}
public double getMarkerSmooth() {
return this.markerSmooth;
}
public String getMarkerTransform() {
return this.markerTransform;
}
public String getMarkerType() {
return this.markerType;
}
public String getMarkerVerticalAlignment() {
return this.markerVerticalAlignment;
}
public Measure<Length> getMarkerWidth() {
return this.markerWidth;
}
public boolean isMarkerAllowOverlap() {
return this.markerAllowOverlap;
}
public boolean isMarkerClip() {
return this.markerClip;
}
public Icon newIcon() {
return this.marker.newIcon(this);
}
public void setMarker(final AbstractMarker marker, final double markerSize, final Color lineColor,
final Color fillColor) {
setMarker(marker);
setMarkerWidth(Measure.valueOf(markerSize, NonSI.PIXEL));
setMarkerHeight(Measure.valueOf(markerSize, NonSI.PIXEL));
setMarkerLineColor(lineColor);
setMarkerHorizontalAlignment("center");
setMarkerVerticalAlignment("middle");
setMarkerFill(fillColor);
}
public void setMarker(final Marker marker) {
final Marker oldMarker = this.marker;
final String oldMarkerType = this.markerType;
this.marker = getWithDefault(marker, ELLIPSE);
if (marker != null && marker.isUseMarkerType()) {
this.markerType = marker.getMarkerType();
} else {
this.markerType = "ellipse";
}
firePropertyChange("marker", oldMarker, this.marker);
firePropertyChange("markerType", oldMarkerType, this.markerType);
}
@SuppressWarnings("unchecked")
public <V extends MarkerStyle> V setMarker(final String markerName, final double markerSize,
final Color lineColor, final double lineWidth, final Color fillColor) {
final AbstractMarker marker = new ShapeMarker(markerName);
setMarker(marker, markerSize, lineColor, fillColor);
setMarkerLineWidth(Measure.valueOf(lineWidth, NonSI.PIXEL));
return (V)this;
}
public void setMarkerAllowOverlap(final boolean markerAllowOverlap) {
final Object oldValue = this.markerAllowOverlap;
this.markerAllowOverlap = markerAllowOverlap;
firePropertyChange("markerAllowOverlap", oldValue, this.markerAllowOverlap);
}
public void setMarkerClip(final boolean markerClip) {
final Object oldValue = this.markerClip;
this.markerClip = markerClip;
firePropertyChange("markerClip", oldValue, this.markerClip);
}
public void setMarkerCompOp(final String markerCompOp) {
final Object oldValue = this.markerCompOp;
this.markerCompOp = markerCompOp;
firePropertyChange("markerCompOp", oldValue, this.markerCompOp);
}
public void setMarkerDx(final double markerDx) {
setMarkerDx(Measure.valueOf(markerDx, NonSI.PIXEL));
}
public void setMarkerDx(final Measure<Length> markerDx) {
final Object oldValue = this.markerDy;
if (markerDx == null) {
this.markerDx = this.markerDy;
} else {
this.markerDx = markerDx;
}
firePropertyChange("markerDx", oldValue, this.markerDx);
updateMarkerDeltaUnits(this.markerDx.getUnit());
}
public void setMarkerDy(final double markerDy) {
setMarkerDy(Measure.valueOf(markerDy, NonSI.PIXEL));
}
public void setMarkerDy(final Measure<Length> markerDy) {
final Object oldValue = this.markerDy;
if (markerDy == null) {
this.markerDy = this.markerDx;
} else {
this.markerDy = markerDy;
}
firePropertyChange("markerDy", oldValue, this.markerDy);
updateMarkerDeltaUnits(this.markerDy.getUnit());
}
public void setMarkerFile(final String markerFile) {
final Object oldMarkerFile = this.markerFile;
// TODO property change
this.markerFile = markerFile;
final Pattern pattern = Pattern.compile("url\\('?([^']+)'?\\)");
String url;
final Matcher matcher = pattern.matcher(markerFile);
if (matcher.find()) {
url = matcher.group(1);
} else {
url = markerFile;
}
if (url.toUpperCase().matches("[A-Z][A-Z0-9\\+\\.\\-]*:")) {
this.markerFileResource = Resource.getResource(url);
} else {
this.markerFileResource = Resource.getBaseResource(url);
}
firePropertyChange("markerFile", oldMarkerFile, markerFile);
setMarker(new ImageMarker(this.markerFileResource));
}
public void setMarkerFill(final Color markerFill) {
final Object oldMarkerFill = this.markerFill;
final Object oldMarkerFillOpacity = this.markerFillOpacity;
if (markerFill == null) {
this.markerFill = new Color(128, 128, 128, this.markerFillOpacity);
} else {
this.markerFill = markerFill;
this.markerFillOpacity = markerFill.getAlpha();
}
firePropertyChange("markerFill", oldMarkerFill, this.markerFill);
firePropertyChange("markerFillOpacity", oldMarkerFillOpacity, this.markerFillOpacity);
}
public void setMarkerFillOpacity(final double markerFillOpacity) {
if (markerFillOpacity < 0 || markerFillOpacity > 1) {
throw new IllegalArgumentException("The opacity must be between 0.0 - 1.0");
} else {
setMarkerFillOpacity((int)(255 * markerFillOpacity));
}
}
public void setMarkerFillOpacity(final int markerFillOpacity) {
if (markerFillOpacity < 0 || markerFillOpacity > 255) {
throw new IllegalArgumentException("The opacity must be between 0 - 255");
} else {
final Object oldMarkerFill = this.markerFill;
final Object oldMarkerFillOpacity = this.markerFillOpacity;
this.markerFillOpacity = markerFillOpacity;
this.markerFill = WebColors.newAlpha(this.markerFill, this.markerFillOpacity);
firePropertyChange("markerFill", oldMarkerFill, this.markerFill);
firePropertyChange("markerFillOpacity", oldMarkerFillOpacity, this.markerFillOpacity);
}
}
public boolean setMarkerFillStyle(final Viewport2D viewport, final Graphics2D graphics) {
if (this.markerFill.getAlpha() == 0) {
return false;
} else {
graphics.setPaint(this.markerFill);
return true;
}
}
public void setMarkerHeight(final Measure<Length> markerHeight) {
final Object oldValue = this.markerHeight;
if (markerHeight == null) {
this.markerHeight = this.markerWidth;
} else {
this.markerHeight = markerHeight;
}
firePropertyChange("markerHeight", oldValue, this.markerHeight);
updateMarkerUnits(this.markerHeight.getUnit());
}
public void setMarkerHorizontalAlignment(final String markerHorizontalAlignment) {
final Object oldValue = this.markerHorizontalAlignment;
this.markerHorizontalAlignment = getWithDefault(markerHorizontalAlignment, "center");
firePropertyChange("markerHorizontalAlignment", oldValue, this.markerHorizontalAlignment);
}
public void setMarkerIgnorePlacement(final String markerIgnorePlacement) {
final Object oldValue = this.markerIgnorePlacement;
this.markerIgnorePlacement = markerIgnorePlacement;
firePropertyChange("markerIgnorePlacement", oldValue, this.markerIgnorePlacement);
}
public void setMarkerLineColor(final Color markerLineColor) {
final Object oldMarkerLineColor = this.markerLineColor;
final Object oldMarkerLineOpacity = this.markerLineOpacity;
if (markerLineColor == null) {
this.markerLineColor = new Color(128, 128, 128, this.markerLineOpacity);
} else {
this.markerLineColor = markerLineColor;
this.markerLineOpacity = markerLineColor.getAlpha();
}
firePropertyChange("markerLineColor", oldMarkerLineColor, this.markerLineColor);
firePropertyChange("markerLineOpacity", oldMarkerLineOpacity, this.markerLineOpacity);
}
public void setMarkerLineOpacity(final double markerLineOpacity) {
if (markerLineOpacity < 0 || markerLineOpacity > 1) {
throw new IllegalArgumentException("The opacity must be between 0.0 - 1.0");
} else {
setMarkerLineOpacity((int)(255 * markerLineOpacity));
}
}
public void setMarkerLineOpacity(final int markerLineOpacity) {
if (markerLineOpacity < 0 || markerLineOpacity > 255) {
throw new IllegalArgumentException("The opacity must be between 0 - 255");
} else {
final Object oldMarkerLineColor = this.markerLineColor;
final Object oldMarkerLineOpacity = this.markerLineOpacity;
this.markerLineOpacity = markerLineOpacity;
this.markerLineColor = WebColors.newAlpha(this.markerLineColor, this.markerLineOpacity);
firePropertyChange("markerLineColor", oldMarkerLineColor, this.markerLineColor);
firePropertyChange("markerLineOpacity", oldMarkerLineOpacity, this.markerLineOpacity);
}
}
public boolean setMarkerLineStyle(final Viewport2D viewport, final Graphics2D graphics) {
final Color color = getMarkerLineColor();
if (color.getAlpha() == 0) {
return false;
} else {
graphics.setColor(color);
final float width = (float)Viewport2D.toDisplayValue(viewport, this.markerLineWidth);
final BasicStroke basicStroke = new BasicStroke(width);
graphics.setStroke(basicStroke);
return true;
}
}
public void setMarkerLineWidth(final Measure<Length> markerLineWidth) {
final Object oldValue = this.markerLineWidth;
if (markerLineWidth == null) {
this.markerLineWidth = Measure.valueOf(1, this.markerWidth.getUnit());
} else {
this.markerLineWidth = markerLineWidth;
}
firePropertyChange("markerLineWidth", oldValue, this.markerLineWidth);
updateMarkerUnits(this.markerLineWidth.getUnit());
}
public void setMarkerOpacity(final double markerOpacity) {
if (markerOpacity < 0 || markerOpacity > 1) {
throw new IllegalArgumentException("The opacity must be between 0.0 - 1.0");
} else {
setMarkerOpacity((int)(255 * markerOpacity));
}
}
public void setMarkerOpacity(final int markerOpacity) {
if (markerOpacity < 0 || markerOpacity > 255) {
throw new IllegalArgumentException("The opacity must be between 0 - 255");
} else {
final Object oldValue = this.markerOpacity;
this.markerOpacity = markerOpacity;
firePropertyChange("markerOpacity", oldValue, this.markerOpacity);
setMarkerLineOpacity(markerOpacity);
setMarkerFillOpacity(markerOpacity);
}
}
public void setMarkerOrientation(final double markerOrientation) {
final Object oldValue = this.markerOrientation;
this.markerOrientation = markerOrientation;
firePropertyChange("markerOrientation", oldValue, this.markerOrientation);
}
public void setMarkerOrientationType(final String markerOrientationType) {
final Object oldValue = this.markerOrientationType;
this.markerOrientationType = getWithDefault(markerOrientationType, "auto");
firePropertyChange("markerOrientationType", oldValue, this.markerOrientationType);
}
public void setMarkerPlacement(final String markerPlacementType) {
setMarkerPlacementType(markerPlacementType);
}
public void setMarkerPlacementType(String markerPlacementType) {
final Object oldValue = this.markerPlacementType;
markerPlacementType = Strings.replaceAll(markerPlacementType, "^point\\(", "vertex\\(");
this.markerPlacementType = getWithDefault(markerPlacementType, "auto");
firePropertyChange("markerPlacementType", oldValue, this.markerPlacementType);
}
public void setMarkerSmooth(final double markerSmooth) {
final Object oldValue = this.markerSmooth;
this.markerSmooth = markerSmooth;
firePropertyChange("markerSmooth", oldValue, this.markerSmooth);
}
public void setMarkerTransform(final String markerTransform) {
final Object oldValue = this.markerTransform;
this.markerTransform = markerTransform;
firePropertyChange("markerTransform", oldValue, this.markerTransform);
}
public void setMarkerType(final String markerType) {
final Object oldValue = this.markerType;
this.markerType = getWithDefault(markerType, "ellipse");
firePropertyChange("markerType", oldValue, this.markerType);
final Marker marker = SymbolLibrary.newMarker(markerType);
setMarker(marker);
}
public void setMarkerVerticalAlignment(final String markerVerticalAlignment) {
final Object oldValue = this.markerVerticalAlignment;
this.markerVerticalAlignment = getWithDefault(markerVerticalAlignment, "middle");
firePropertyChange("markerVerticalAlignment", oldValue, this.markerVerticalAlignment);
}
public void setMarkerWidth(final Measure<Length> markerWidth) {
final Object oldValue = this.markerWidth;
if (markerWidth == null) {
this.markerWidth = this.markerHeight;
} else {
this.markerWidth = markerWidth;
}
firePropertyChange("markerWidth", oldValue, this.markerWidth);
updateMarkerUnits(this.markerWidth.getUnit());
}
@Override
public void setPropertyError(final String name, final Object value, final Throwable e) {
Logs.error(this, "Error setting " + name + '=' + value, e);
}
@Override
public MapEx toMap() {
final boolean geometryStyle = this instanceof GeometryStyle;
final MapEx map = new LinkedHashMapEx();
for (final String name : PROPERTY_NAMES) {
if (geometryStyle || name.startsWith("marker")) {
final Object value = Property.get(this, name);
boolean defaultEqual = false;
if (DEFAULT_VALUES.containsKey(name)) {
final Object defaultValue = DEFAULT_VALUES.get(name);
defaultEqual = DataType.equal(defaultValue, value);
}
if (!defaultEqual) {
addToMap(map, name, value);
}
}
}
return map;
}
@Override
public String toString() {
return toMap().toString();
}
private void updateMarkerDeltaUnits(final Unit<Length> unit) {
if (!this.markerDx.getUnit().equals(unit)) {
final double oldValue = this.markerDx.getValue().doubleValue();
final Measure<Length> newValue = Measure.valueOf(oldValue, unit);
setMarkerDx(newValue);
}
if (!this.markerDy.getUnit().equals(unit)) {
final double oldValue = this.markerDy.getValue().doubleValue();
final Measure<Length> newValue = Measure.valueOf(oldValue, unit);
setMarkerDy(newValue);
}
}
private void updateMarkerUnits(final Unit<Length> unit) {
if (!this.markerWidth.getUnit().equals(unit)) {
final double oldValue = this.markerWidth.getValue().doubleValue();
final Measure<Length> newValue = Measure.valueOf(oldValue, unit);
setMarkerWidth(newValue);
}
if (!this.markerHeight.getUnit().equals(unit)) {
final double oldValue = this.markerHeight.getValue().doubleValue();
final Measure<Length> newValue = Measure.valueOf(oldValue, unit);
setMarkerHeight(newValue);
}
if (!this.markerLineWidth.getUnit().equals(unit)) {
final double oldValue = this.markerLineWidth.getValue().doubleValue();
final Measure<Length> newValue = Measure.valueOf(oldValue, unit);
setMarkerLineWidth(newValue);
}
}
}