package com.revolsys.swing.map.layer.record.renderer;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.function.Predicate;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import javax.swing.Icon;
import com.revolsys.collection.map.MapEx;
import com.revolsys.geometry.model.BoundingBox;
import com.revolsys.geometry.model.Geometry;
import com.revolsys.geometry.model.GeometryFactory;
import com.revolsys.geometry.model.LineString;
import com.revolsys.geometry.model.Point;
import com.revolsys.geometry.model.Polygon;
import com.revolsys.geometry.model.TopologyException;
import com.revolsys.geometry.model.impl.PointDoubleXYOrientation;
import com.revolsys.geometry.model.segment.LineSegment;
import com.revolsys.geometry.model.segment.Segment;
import com.revolsys.geometry.model.vertex.Vertex;
import com.revolsys.io.BaseCloseable;
import com.revolsys.io.map.MapObjectFactory;
import com.revolsys.io.map.MapObjectFactoryRegistry;
import com.revolsys.logging.Logs;
import com.revolsys.predicate.Predicates;
import com.revolsys.record.Record;
import com.revolsys.record.filter.MultipleAttributeValuesFilter;
import com.revolsys.record.schema.RecordDefinition;
import com.revolsys.record.schema.RecordDefinitionProxy;
import com.revolsys.swing.map.Viewport2D;
import com.revolsys.swing.map.layer.AbstractLayer;
import com.revolsys.swing.map.layer.AbstractLayerRenderer;
import com.revolsys.swing.map.layer.LayerRenderer;
import com.revolsys.swing.map.layer.grid.GridLayerRenderer;
import com.revolsys.swing.map.layer.menu.TreeItemScaleMenu;
import com.revolsys.swing.map.layer.record.AbstractRecordLayer;
import com.revolsys.swing.map.layer.record.LayerRecord;
import com.revolsys.swing.map.layer.record.RecordDefinitionSqlFilter;
import com.revolsys.swing.menu.MenuFactory;
import com.revolsys.swing.menu.Menus;
import com.revolsys.util.Cancellable;
import com.revolsys.util.JavaBeanUtil;
import com.revolsys.util.Property;
public abstract class AbstractRecordLayerRenderer extends AbstractLayerRenderer<AbstractRecordLayer>
implements RecordDefinitionProxy {
public static final Pattern PATTERN_INDEX_FROM_END = Pattern.compile("n(?:\\s*-\\s*(\\d+)\\s*)?");
public static final Pattern PATTERN_SEGMENT_INDEX = Pattern.compile("segment\\((.*)\\)");
public static final Pattern PATTERN_VERTEX_INDEX = Pattern.compile("vertex\\((.*)\\)");
static {
final MenuFactory menu = MenuFactory.getMenu(AbstractRecordLayerRenderer.class);
Menus.addMenuItem(menu, "layer", "View/Edit Style", "palette",
((Predicate<AbstractRecordLayerRenderer>)AbstractRecordLayerRenderer::isEditing).negate(),
AbstractRecordLayerRenderer::showProperties, false);
Menus.addMenuItem(menu, "layer", "Delete", "delete", AbstractRecordLayerRenderer::isHasParent,
AbstractRecordLayerRenderer::delete, true);
menu.addComponentFactory("scale", new TreeItemScaleMenu<>(true, null,
AbstractRecordLayerRenderer::getMinimumScale, AbstractRecordLayerRenderer::setMinimumScale));
menu.addComponentFactory("scale", new TreeItemScaleMenu<>(false, null,
AbstractRecordLayerRenderer::getMaximumScale, AbstractRecordLayerRenderer::setMaximumScale));
Menus.addMenuItem(menu, "wrap", "Wrap With Multiple Style", "style_multiple_wrap",
AbstractRecordLayerRenderer::wrapWithMultipleStyle, false);
Menus.addMenuItem(menu, "wrap", "Wrap With Filter Style", "style_filter_wrap",
AbstractRecordLayerRenderer::wrapWithFilterStyle, false);
Menus.addMenuItem(menu, "wrap", "Wrap With Scale Style", "style_scale_wrap",
AbstractRecordLayerRenderer::wrapWithScaleStyle, false);
}
public static Predicate<Record> getFilter(final RecordDefinitionProxy recordDefinitionProxy,
final Map<String, ? extends Object> properties) {
@SuppressWarnings("unchecked")
Map<String, Object> filterDefinition = (Map<String, Object>)properties.get("filter");
if (filterDefinition != null) {
filterDefinition = new LinkedHashMap<>(filterDefinition);
final String type = MapObjectFactory.getType(filterDefinition);
if ("valueFilter".equals(type)) {
return new MultipleAttributeValuesFilter(filterDefinition);
} else if ("queryFilter".equals(type)) {
String query = (String)filterDefinition.remove("query");
if (Property.hasValue(query)) {
query = query.replaceAll("!= null", "IS NOT NULL");
query = query.replaceAll("== null", "IS NULL");
query = query.replaceAll("==", "=");
query = query.replaceAll("!=", "<>");
query = query.replaceAll("\\{(.*)\\}.contains\\((.*)\\)", "$2 IN ($1)");
query = query.replaceAll("\\[(.*)\\]", "$1");
query = query.replaceAll("(.*).startsWith\\('(.*)'\\)", "$1 LIKE '$2%'");
query = query.replaceAll("#systemProperties\\['user.name'\\]", "'{gbaUsername}'");
return new RecordDefinitionSqlFilter(recordDefinitionProxy, query);
}
} else if ("sqlFilter".equals(type)) {
final String query = (String)filterDefinition.remove("query");
if (Property.hasValue(query)) {
return new RecordDefinitionSqlFilter(recordDefinitionProxy, query);
}
} else {
Logs.error(AbstractRecordLayerRenderer.class, "Unknown filter type " + type);
}
}
return Predicates.all();
}
protected static int getIndex(final Matcher matcher) {
int index;
final String argument = matcher.group(1);
if (PATTERN_INDEX_FROM_END.matcher(argument).matches()) {
final String indexString = argument.replaceAll("[^0-9\\-]+", "");
if (indexString.isEmpty()) {
index = -1;
} else {
index = Integer.parseInt(indexString) - 1;
}
} else {
index = Integer.parseInt(argument);
}
return index;
}
public static PointDoubleXYOrientation getPointWithOrientation(final Viewport2D viewport,
final Geometry geometry, final String placementType) {
if (viewport == null) {
return new PointDoubleXYOrientation(0.0, 0.0, 0);
} else {
final GeometryFactory viewportGeometryFactory2d = viewport.getGeometryFactory2dFloating();
if (viewportGeometryFactory2d != null && geometry != null && !geometry.isEmpty()) {
Point point = null;
double orientation = 0;
if (geometry instanceof Point) {
point = (Point)geometry;
} else {
final Matcher vertexIndexMatcher = PATTERN_VERTEX_INDEX.matcher(placementType);
if (vertexIndexMatcher.matches()) {
final int vertexCount = geometry.getVertexCount();
final int vertexIndex = getIndex(vertexIndexMatcher);
if (vertexIndex >= -vertexCount && vertexIndex < vertexCount) {
final Vertex vertex = geometry.getVertex(vertexIndex);
orientation = vertex.getOrientaton(viewportGeometryFactory2d);
point = vertex.convertGeometry(viewportGeometryFactory2d);
}
} else {
final Matcher segmentIndexMatcher = PATTERN_SEGMENT_INDEX.matcher(placementType);
if (segmentIndexMatcher.matches()) {
final int segmentCount = geometry.getSegmentCount();
if (segmentCount > 0) {
final int index = getIndex(segmentIndexMatcher);
LineSegment segment = geometry.getSegment(index);
segment = segment.convertGeometry(viewportGeometryFactory2d);
if (segment != null) {
point = segment.midPoint();
orientation = segment.getOrientaton();
}
}
} else {
PointDoubleXYOrientation pointDoubleXYOrientation = getPointWithOrientationCentre(
viewportGeometryFactory2d, geometry);
if (!viewport.getBoundingBox().covers(pointDoubleXYOrientation)) {
try {
final Geometry clippedGeometry = viewport.getBoundingBox()
.toPolygon()
.intersection(geometry);
if (!clippedGeometry.isEmpty()) {
double maxArea = 0;
double maxLength = 0;
for (int i = 0; i < clippedGeometry.getGeometryCount(); i++) {
final Geometry part = clippedGeometry.getGeometry(i);
if (part instanceof Polygon) {
final double area = part.getArea();
if (area > maxArea) {
maxArea = area;
pointDoubleXYOrientation = getPointWithOrientationCentre(
viewportGeometryFactory2d, part);
}
} else if (part instanceof LineString) {
if (maxArea == 0 && "auto".equals(placementType)) {
final double length = part.getLength();
if (length > maxLength) {
maxLength = length;
pointDoubleXYOrientation = getPointWithOrientationCentre(
viewportGeometryFactory2d, part);
}
}
} else if (part instanceof Point) {
if (maxArea == 0 && maxLength == 0 && "auto".equals(placementType)) {
pointDoubleXYOrientation = getPointWithOrientationCentre(
viewportGeometryFactory2d, part);
}
}
}
}
} catch (final Throwable t) {
}
}
return pointDoubleXYOrientation;
}
}
}
if (Property.hasValue(point)) {
if (viewport.getBoundingBox().covers(point)) {
point = point.convertPoint2d(viewportGeometryFactory2d);
return new PointDoubleXYOrientation(point, orientation);
}
}
}
return null;
}
}
private static PointDoubleXYOrientation getPointWithOrientationCentre(
final GeometryFactory geometryFactory2dFloating, final Geometry geometry) {
double orientation = 0;
Point point = null;
if (geometry instanceof LineString) {
final LineString line = geometry.convertGeometry(geometryFactory2dFloating, 2);
final double totalLength = line.getLength();
final double centreLength = totalLength / 2;
double currentLength = 0;
for (final Segment segment : line.segments()) {
final double segmentLength = segment.getLength();
currentLength += segmentLength;
if (currentLength >= centreLength) {
final double segmentFraction = 1 - (currentLength - centreLength) / segmentLength;
point = segment.pointAlong(segmentFraction);
orientation = segment.getOrientaton();
break;
}
}
} else {
point = geometry.getPointWithin();
point = point.convertPoint2d(geometryFactory2dFloating);
}
return new PointDoubleXYOrientation(point, orientation);
}
public static void mapObjectFactoryInit() {
MapObjectFactoryRegistry.newFactory("geometryStyle", GeometryStyleRenderer::new);
MapObjectFactoryRegistry.newFactory("textStyle", TextStyleRenderer::new);
MapObjectFactoryRegistry.newFactory("markerStyle", MarkerStyleRenderer::new);
MapObjectFactoryRegistry.newFactory("multipleStyle", MultipleRenderer::new);
MapObjectFactoryRegistry.newFactory("scaleStyle", ScaleMultipleRenderer::new);
MapObjectFactoryRegistry.newFactory("filterStyle", FilterMultipleRenderer::new);
MapObjectFactoryRegistry.newFactory("gridLayerRenderer", GridLayerRenderer::new);
}
private Predicate<Record> filter = Predicates.all();
public AbstractRecordLayerRenderer(final String type, final String name) {
super(type, name);
}
public AbstractRecordLayerRenderer(final String type, final String name,
final AbstractRecordLayer layer, final LayerRenderer<?> parent) {
super(type, name, layer, parent);
}
@Override
public AbstractRecordLayerRenderer clone() {
final AbstractRecordLayerRenderer clone = (AbstractRecordLayerRenderer)super.clone();
clone.filter = JavaBeanUtil.clone(this.filter);
return clone;
}
public void delete() {
final LayerRenderer<?> parent = getParent();
if (parent instanceof AbstractMultipleRenderer) {
final AbstractMultipleRenderer multiple = (AbstractMultipleRenderer)parent;
multiple.removeRenderer(this);
}
}
public String getQueryFilter() {
if (this.filter instanceof RecordDefinitionSqlFilter) {
final RecordDefinitionSqlFilter layerFilter = (RecordDefinitionSqlFilter)this.filter;
return layerFilter.getQuery();
} else {
return null;
}
}
@Override
public RecordDefinition getRecordDefinition() {
final AbstractRecordLayer layer = getLayer();
if (layer == null) {
return null;
} else {
return layer.getRecordDefinition();
}
}
protected boolean isFilterAccept(final LayerRecord record) {
try {
return this.filter.test(record);
} catch (final Throwable e) {
return false;
}
}
public boolean isHasParent() {
return getParent() != null;
}
public boolean isVisible(final LayerRecord record) {
if (isVisible() && !record.isDeleted()) {
final boolean filterAccept = isFilterAccept(record);
return filterAccept;
} else {
return false;
}
}
public Icon newIcon() {
return getIcon();
}
protected void refreshIcon() {
final Icon icon = newIcon();
setIcon(icon);
}
@Override
public void render(final Viewport2D viewport, final Cancellable cancellable,
final AbstractRecordLayer layer) {
if (layer.hasGeometryField()) {
final BoundingBox boundingBox = viewport.getBoundingBox();
final List<LayerRecord> records = layer.getRecordsBackground(boundingBox);
try (
BaseCloseable transformCloseable = viewport.setUseModelCoordinates(true)) {
renderRecords(viewport, cancellable, layer, records);
}
}
}
public void renderRecord(final Viewport2D viewport, final BoundingBox visibleArea,
final AbstractLayer layer, final LayerRecord record) {
}
protected void renderRecords(final Viewport2D viewport, final Cancellable cancellable,
final AbstractRecordLayer layer, final List<LayerRecord> records) {
final BoundingBox visibleArea = viewport.getBoundingBox();
for (final LayerRecord record : cancellable.cancellable(records)) {
if (record != null) {
if (isVisible(record) && !layer.isHidden(record)) {
try {
renderRecord(viewport, visibleArea, layer, record);
} catch (final TopologyException e) {
} catch (final Throwable e) {
if (!cancellable.isCancelled()) {
Logs.error(this,
"Unabled to render " + layer.getName() + " #" + record.getIdentifier(), e);
}
}
}
}
}
}
public void renderSelectedRecord(final Viewport2D viewport, final AbstractLayer layer,
final LayerRecord record) {
final BoundingBox boundingBox = viewport.getBoundingBox();
if (isVisible(record)) {
try {
renderRecord(viewport, boundingBox, layer, record);
} catch (final TopologyException e) {
}
}
}
protected void replace(final AbstractLayer layer, final AbstractMultipleRenderer parent,
final AbstractMultipleRenderer newRenderer) {
if (parent == null) {
if (isEditing()) {
newRenderer.setEditing(true);
firePropertyChange("replaceRenderer", this, newRenderer);
} else {
layer.setRenderer(newRenderer);
}
} else {
final int index = parent.removeRenderer(this);
parent.addRenderer(index, newRenderer);
}
}
protected void setFilter(final Predicate<Record> filter) {
final Object oldValue = this.filter;
this.filter = filter;
firePropertyChange("filter", oldValue, filter);
}
@Override
public void setName(final String name) {
final AbstractMultipleRenderer parent = (AbstractMultipleRenderer)getParent();
String newName = name;
if (parent != null) {
int i = 1;
while (parent.hasRendererWithSameName(this, newName)) {
newName = name + i;
i++;
}
}
super.setName(newName);
}
@Override
public void setProperties(final Map<String, ? extends Object> properties) {
super.setProperties(properties);
this.filter = getFilter(this, properties);
}
public void setQueryFilter(final String query) {
if (this.filter instanceof RecordDefinitionSqlFilter
|| this.filter == Predicates.<Record> all()) {
Predicate<Record> filter;
if (Property.hasValue(query)) {
filter = new RecordDefinitionSqlFilter(this, query);
} else {
filter = Predicates.all();
}
setFilter(filter);
}
}
@Override
public MapEx toMap() {
final MapEx map = super.toMap();
if (!(this.filter == Predicates.<Record> all())) {
addToMap(map, "filter", this.filter);
}
return map;
}
protected void wrap(final AbstractLayer layer, final AbstractMultipleRenderer parent,
final AbstractMultipleRenderer newRenderer) {
newRenderer.addRenderer(this.clone());
replace(layer, parent, newRenderer);
}
public FilterMultipleRenderer wrapWithFilterStyle() {
final AbstractRecordLayer layer = getLayer();
final AbstractMultipleRenderer parent = (AbstractMultipleRenderer)getParent();
final FilterMultipleRenderer newRenderer = new FilterMultipleRenderer(layer, parent);
wrap(layer, parent, newRenderer);
return newRenderer;
}
public MultipleRenderer wrapWithMultipleStyle() {
final AbstractRecordLayer layer = getLayer();
final AbstractMultipleRenderer parent = (AbstractMultipleRenderer)getParent();
final MultipleRenderer newRenderer = new MultipleRenderer(layer, parent);
wrap(layer, parent, newRenderer);
return newRenderer;
}
public ScaleMultipleRenderer wrapWithScaleStyle() {
final AbstractRecordLayer layer = getLayer();
final AbstractMultipleRenderer parent = (AbstractMultipleRenderer)getParent();
final ScaleMultipleRenderer newRenderer = new ScaleMultipleRenderer(layer, parent);
wrap(layer, parent, newRenderer);
return newRenderer;
}
}