package com.revolsys.gis.parallel;
import java.util.ArrayList;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;
import java.util.function.Function;
import java.util.function.Predicate;
import com.revolsys.geometry.algorithm.linematch.LineMatchGraph;
import com.revolsys.geometry.filter.LineEqualIgnoreDirectionFilter;
import com.revolsys.geometry.filter.LineIntersectsFilter;
import com.revolsys.geometry.index.PointRecordMap;
import com.revolsys.geometry.index.quadtree.RecordQuadTree;
import com.revolsys.geometry.model.Geometry;
import com.revolsys.geometry.model.LineString;
import com.revolsys.geometry.model.Lineal;
import com.revolsys.geometry.model.Point;
import com.revolsys.parallel.channel.Channel;
import com.revolsys.predicate.Predicates;
import com.revolsys.record.Record;
import com.revolsys.record.RecordLog;
import com.revolsys.record.Records;
import com.revolsys.record.filter.RecordGeometryFilter;
import com.revolsys.record.schema.RecordDefinition;
import com.revolsys.util.count.LabelCountMap;
public class CompareProcessor extends AbstractMergeProcess {
private final boolean cleanDuplicatePoints = true;
private LabelCountMap duplicateOtherStatistics = new LabelCountMap("Duplicate Other");
private LabelCountMap duplicateSourceStatistics = new LabelCountMap("Duplicate Source");
private Function<Record, Predicate<Record>> equalFilterFactory;
private LabelCountMap equalStatistics = new LabelCountMap("Equal");
private Predicate<Record> excludeFilter;
private LabelCountMap excludeNotEqualOtherStatistics = new LabelCountMap(
"Exclude Not Equal Other");
private LabelCountMap excludeNotEqualSourceStatistics = new LabelCountMap(
"Exclude Not Equal Source");
private String label;
private boolean logNotEqualSource = true;
private LabelCountMap notEqualOtherStatistics = new LabelCountMap("Not Equal Other");
private LabelCountMap notEqualSourceStatistics = new LabelCountMap("Not Equal Source");
private RecordQuadTree<Record> otherIndex;
private PointRecordMap otherPointMap = new PointRecordMap();
private Set<Record> sourceObjects = new LinkedHashSet<>();
private final PointRecordMap sourcePointMap = new PointRecordMap();
@Override
protected void addOtherObject(final Record record) {
final Geometry geometry = record.getGeometry();
if (geometry instanceof Point) {
boolean add = true;
if (this.cleanDuplicatePoints) {
final List<Record> objects = this.otherPointMap.getRecords(record);
if (!objects.isEmpty()) {
final Predicate<Record> filter = this.equalFilterFactory.apply(record);
add = !Predicates.matches(objects, filter);
}
if (add) {
this.otherPointMap.addRecord(record);
} else {
this.duplicateOtherStatistics.addCount(record);
}
}
} else if (geometry instanceof LineString) {
this.otherIndex.addRecord(record);
}
}
@Override
protected void addSourceObject(final Record object) {
final Geometry geometry = object.getGeometry();
if (geometry instanceof Point) {
boolean add = true;
if (this.cleanDuplicatePoints) {
final List<Record> objects = this.sourcePointMap.getRecords(object);
if (!objects.isEmpty()) {
final Predicate<Record> filter = this.equalFilterFactory.apply(object);
add = !Predicates.matches(objects, filter);
}
}
if (add) {
this.sourcePointMap.addRecord(object);
} else {
this.duplicateSourceStatistics.addCount(object);
}
} else if (geometry instanceof LineString) {
this.sourceObjects.add(object);
}
}
public LabelCountMap getDuplicateOtherStatistics() {
return this.duplicateOtherStatistics;
}
public LabelCountMap getDuplicateSourceStatistics() {
return this.duplicateSourceStatistics;
}
public Function<Record, Predicate<Record>> getEqualFilterFactory() {
return this.equalFilterFactory;
}
public LabelCountMap getEqualStatistics() {
return this.equalStatistics;
}
public Predicate<Record> getExcludeFilter() {
return this.excludeFilter;
}
public LabelCountMap getExcludeNotEqualOtherStatistics() {
return this.excludeNotEqualOtherStatistics;
}
public LabelCountMap getExcludeNotEqualSourceStatistics() {
return this.excludeNotEqualSourceStatistics;
}
public String getLabel() {
return this.label;
}
public LabelCountMap getNotEqualOtherStatistics() {
return this.notEqualOtherStatistics;
}
public LabelCountMap getNotEqualSourceStatistics() {
return this.notEqualSourceStatistics;
}
@Override
protected void init(final RecordDefinition recordDefinition) {
super.init(recordDefinition);
this.otherIndex = new RecordQuadTree<>(recordDefinition.getGeometryFactory());
}
public boolean isLogNotEqualSource() {
return this.logNotEqualSource;
}
private void logError(final Record object, final String message, final boolean source) {
if (this.excludeFilter == null || !this.excludeFilter.test(object)) {
if (source) {
this.notEqualSourceStatistics.addCount(object);
} else {
this.notEqualOtherStatistics.addCount(object);
}
RecordLog.error(getClass(), message, object);
} else {
if (source) {
this.excludeNotEqualSourceStatistics.addCount(object);
} else {
this.excludeNotEqualOtherStatistics.addCount(object);
}
}
}
private void processExactLineMatch(final Record sourceObject) {
final LineString sourceLine = sourceObject.getGeometry();
final LineEqualIgnoreDirectionFilter lineEqualFilter = new LineEqualIgnoreDirectionFilter(
sourceLine, 3);
final Predicate<Record> geometryFilter = new RecordGeometryFilter<>(lineEqualFilter);
final Predicate<Record> equalFilter = this.equalFilterFactory.apply(sourceObject);
final Predicate<Record> filter = equalFilter.and(geometryFilter);
final Record otherObject = this.otherIndex.queryFirst(sourceObject, filter);
if (otherObject != null) {
this.equalStatistics.addCount(sourceObject);
removeObject(sourceObject);
removeOtherObject(otherObject);
}
}
private void processExactLineMatches() {
for (final Record object : new ArrayList<>(this.sourceObjects)) {
processExactLineMatch(object);
}
}
private void processExactPointMatch(final Record sourceObject) {
final Predicate<Record> equalFilter = this.equalFilterFactory.apply(sourceObject);
final Record otherObject = this.otherPointMap.getFirstMatch(sourceObject, equalFilter);
if (otherObject != null) {
final Point sourcePoint = sourceObject.getGeometry();
final double sourceZ = sourcePoint.getZ();
final Point otherPoint = otherObject.getGeometry();
final double otherZ = otherPoint.getZ();
if (sourceZ == otherZ || Double.isNaN(sourceZ) && Double.isNaN(otherZ)) {
this.equalStatistics.addCount(sourceObject);
removeObject(sourceObject);
removeOtherObject(otherObject);
}
}
}
private void processExactPointMatches() {
for (final Record object : new ArrayList<>(this.sourcePointMap.getAll())) {
processExactPointMatch(object);
}
}
@Override
protected void processObjects(final RecordDefinition recordDefinition,
final Channel<Record> out) {
if (this.otherIndex.size() + this.otherPointMap.size() == 0) {
if (this.logNotEqualSource) {
for (final Record object : this.sourceObjects) {
logError(object, "Source missing in Other", true);
}
}
} else {
processExactPointMatches();
processExactLineMatches();
processPartialMatches();
}
for (final Record object : this.otherIndex.getAll()) {
logError(object, "Other missing in Source", false);
}
for (final Record record : this.otherPointMap.getAll()) {
logError(record, "Other missing in Source", false);
}
if (this.logNotEqualSource) {
for (final Record object : this.sourceObjects) {
logError(object, "Source missing in Other", true);
}
}
this.sourceObjects.clear();
this.otherIndex = null;
this.otherPointMap.clear();
}
private void processPartialMatch(final Record sourceObject) {
final Geometry sourceGeometry = sourceObject.getGeometry();
if (sourceGeometry instanceof LineString) {
final LineString sourceLine = (LineString)sourceGeometry;
final LineIntersectsFilter intersectsFilter = new LineIntersectsFilter(sourceLine);
final Predicate<Record> geometryFilter = new RecordGeometryFilter<>(intersectsFilter);
final Predicate<Record> equalFilter = this.equalFilterFactory.apply(sourceObject);
final Predicate<Record> filter = equalFilter.and(geometryFilter);
final List<Record> otherObjects = this.otherIndex.queryList(sourceGeometry, filter);
if (!otherObjects.isEmpty()) {
final LineMatchGraph<Record> graph = new LineMatchGraph<>(sourceObject, sourceLine);
for (final Record otherObject : otherObjects) {
final LineString otherLine = otherObject.getGeometry();
graph.add(otherLine);
}
final Lineal nonMatchedLines = graph.getNonMatchedLines(0);
if (nonMatchedLines.isEmpty()) {
removeObject(sourceObject);
} else {
removeObject(sourceObject);
if (nonMatchedLines.getGeometryCount() == 1
&& nonMatchedLines.getGeometry(0).getLength() == 1) {
} else {
for (int j = 0; j < nonMatchedLines.getGeometryCount(); j++) {
final Geometry newGeometry = nonMatchedLines.getGeometry(j);
final Record newObject = Records.copy(sourceObject, newGeometry);
addSourceObject(newObject);
}
}
}
for (int i = 0; i < otherObjects.size(); i++) {
final Record otherObject = otherObjects.get(i);
final Lineal otherNonMatched = graph.getNonMatchedLines(i + 1, 0);
for (int j = 0; j < otherNonMatched.getGeometryCount(); j++) {
final Geometry newGeometry = otherNonMatched.getGeometry(j);
final Record newOtherObject = Records.copy(otherObject, newGeometry);
addOtherObject(newOtherObject);
}
removeOtherObject(otherObject);
}
}
}
}
private void processPartialMatches() {
for (final Record object : new ArrayList<>(this.sourceObjects)) {
processPartialMatch(object);
}
}
private void removeObject(final Record object) {
this.sourceObjects.remove(object);
}
private void removeOtherObject(final Record object) {
final Geometry geometry = object.getGeometry();
if (geometry instanceof Point) {
this.otherPointMap.removeRecord(object);
} else {
this.otherIndex.removeRecord(object);
}
}
public void setEqualFilterFactory(final Function<Record, Predicate<Record>> equalFilterFactory) {
this.equalFilterFactory = equalFilterFactory;
}
public void setExcludeFilter(final Predicate<Record> excludeFilter) {
this.excludeFilter = excludeFilter;
}
public void setLabel(final String label) {
this.label = label;
}
public void setLogNotEqualSource(final boolean logNotEqualSource) {
this.logNotEqualSource = logNotEqualSource;
}
@Override
protected void setUp() {
this.equalStatistics.connect();
this.notEqualSourceStatistics.connect();
this.notEqualOtherStatistics.connect();
this.duplicateSourceStatistics.connect();
this.duplicateOtherStatistics.connect();
this.excludeNotEqualSourceStatistics.connect();
this.excludeNotEqualOtherStatistics.connect();
}
@Override
protected void tearDown() {
this.sourceObjects = null;
this.sourcePointMap.clear();
this.otherPointMap = null;
this.otherIndex = null;
this.equalStatistics.disconnect();
this.notEqualSourceStatistics.disconnect();
this.notEqualOtherStatistics.disconnect();
this.duplicateSourceStatistics.disconnect();
this.duplicateOtherStatistics.disconnect();
this.excludeNotEqualSourceStatistics.disconnect();
this.excludeNotEqualOtherStatistics.disconnect();
this.equalStatistics = null;
this.notEqualSourceStatistics = null;
this.notEqualOtherStatistics = null;
this.duplicateSourceStatistics = null;
this.duplicateOtherStatistics = null;
this.excludeNotEqualSourceStatistics = null;
this.excludeNotEqualOtherStatistics = null;
}
}