Skip to content

Commit

Permalink
Merge pull request #1373 from randomboolean/convexHull
Browse files Browse the repository at this point in the history
Convex hull
  • Loading branch information
hbs authored Nov 15, 2024
2 parents e57d6df + ff829a4 commit 9c39183
Show file tree
Hide file tree
Showing 4 changed files with 187 additions and 0 deletions.
83 changes: 83 additions & 0 deletions warp10/src/main/java/io/warp10/continuum/gts/GTSHelper.java
Original file line number Diff line number Diff line change
Expand Up @@ -12478,6 +12478,89 @@ public static GeoTimeSerie lttb(GeoTimeSerie gts, int threshold, boolean timebas
return sampled;
}

/**
* Calculates the 2D cross product of three points represented by their indices in a GeoTimeSerie (GTS).
* The cross product of two vectors (AB and BC) is a scalar that can indicate the relative orientation of the points (A, B, C) in 2D space.
*
* The formula used to calculate the cross product is:
* ((Ax - Bx) * (Cy - By)) - ((Ay - By) * (Cx - Bx))
* This formula calculates the signed area of the parallelogram formed by the vectors AB and BC,
* which helps determine whether point C lies to the left, right, or on the line defined by points A and B.
*
* @param gts The GeoTimeSerie object that contains time ticks and values for the points.
* @param idA The index of point A in the GeoTimeSerie
* @param idB The index of point B in the GeoTimeSerie
* @param idC The index of point C in the GeoTimeSerie
* @return The cross product as a double value. A positive result indicates point C is to the left of AB,
* a negative result indicates point C is to the right, and zero means the points are collinear.
*/
public static double crossProduct(GeoTimeSerie gts, int idA, int idB, int idC) {
double Ax = ((Long) GTSHelper.tickAtIndex(gts, idA)).doubleValue();
double Ay = ((Number) GTSHelper.valueAtIndex(gts, idA)).doubleValue();
double Bx = ((Long) GTSHelper.tickAtIndex(gts, idB)).doubleValue();
double By = ((Number) GTSHelper.valueAtIndex(gts, idB)).doubleValue();
double Cx = ((Long) GTSHelper.tickAtIndex(gts, idC)).doubleValue();
double Cy = ((Number) GTSHelper.valueAtIndex(gts, idC)).doubleValue();

return ((Ax - Bx) * (Cy - By)) - ((Ay - By) * (Cx - Bx));
}

/**
* Implements Andrew's monotone chains algorithm to compute the lower part of the convex hull formed by the gts plot.
* @param gts
* @return res
*/
public static GeoTimeSerie lowerHull(GeoTimeSerie gts) {
GTSHelper.sort(gts, false);

if (gts.size() < 3) {
return gts.clone();
}

List<Integer> currentHull = new ArrayList<>();
for (int i = 0; i < gts.size(); i++) {
while (currentHull.size() >= 2 && crossProduct(gts, currentHull.get(currentHull.size()-2), currentHull.get(currentHull.size()-1), i) > 0) {
currentHull.remove(currentHull.size()-1);
}
currentHull.add(i);
}

GeoTimeSerie res = gts.cloneEmpty();
for (int i: currentHull) {
GTSHelper.setValue(res, tickAtIndex(gts, i), locationAtIndex(gts, i), elevationAtIndex(gts, i), valueAtIndex(gts, i), false);
}

return res;
}

/**
* Implements Andrew's monotone chains algorithm to compute the upper part of the convex hull formed by the gts plot.
* @param gts
* @return res
*/
public static GeoTimeSerie upperHull(GeoTimeSerie gts) {
GTSHelper.sort(gts, false);

if (gts.size() < 3) {
return gts.clone();
}

List<Integer> currentHull = new ArrayList<>();
for (int i = 0; i < gts.size(); i++) {
while (currentHull.size() >= 2 && crossProduct(gts, currentHull.get(currentHull.size()-2), currentHull.get(currentHull.size()-1), i) < 0) {
currentHull.remove(currentHull.size()-1);
}
currentHull.add(i);
}

GeoTimeSerie res = gts.cloneEmpty();
for (int i: currentHull) {
GTSHelper.setValue(res, tickAtIndex(gts, i), locationAtIndex(gts, i), elevationAtIndex(gts, i), valueAtIndex(gts, i), false);
}

return res;
}

public static void dump(GTSEncoder encoder, PrintWriter pw) {
StringBuilder sb = new StringBuilder(" ");
Metadata meta = encoder.getMetadata();
Expand Down
6 changes: 6 additions & 0 deletions warp10/src/main/java/io/warp10/script/WarpScriptLib.java
Original file line number Diff line number Diff line change
Expand Up @@ -373,6 +373,7 @@
import io.warp10.script.functions.LOAD;
import io.warp10.script.functions.LOCATIONS;
import io.warp10.script.functions.LOCSTRINGS;
import io.warp10.script.functions.LOWERHULL;
import io.warp10.script.functions.LOWESS;
import io.warp10.script.functions.LR;
import io.warp10.script.functions.LSORT;
Expand Down Expand Up @@ -653,6 +654,7 @@
import io.warp10.script.functions.UNWRAPENCODER;
import io.warp10.script.functions.UNWRAPSIZE;
import io.warp10.script.functions.UPDATE;
import io.warp10.script.functions.UPPERHULL;
import io.warp10.script.functions.URLDECODE;
import io.warp10.script.functions.URLENCODE;
import io.warp10.script.functions.UUID;
Expand Down Expand Up @@ -1467,6 +1469,8 @@ public class WarpScriptLib {
public static final String FDWT = "FDWT";
public static final String IDWT = "IDWT";
public static final String DWTSPLIT = "DWTSPLIT";
public static final String LOWERHULL = "LOWERHULL";
public static final String UPPERHULL = "UPPERHULL";
public static final String EMPTY = "EMPTY";
public static final String NONEMPTY = "NONEMPTY";
public static final String PARTITION = "PARTITION";
Expand Down Expand Up @@ -2479,6 +2483,8 @@ public class WarpScriptLib {
addNamedWarpScriptFunction(new FDWT(FDWT));
addNamedWarpScriptFunction(new IDWT(IDWT));
addNamedWarpScriptFunction(new DWTSPLIT(DWTSPLIT));
addNamedWarpScriptFunction(new LOWERHULL(LOWERHULL));
addNamedWarpScriptFunction(new UPPERHULL(UPPERHULL));
addNamedWarpScriptFunction(new EMPTY(EMPTY));
addNamedWarpScriptFunction(new NONEMPTY(NONEMPTY));
addNamedWarpScriptFunction(new PARTITION(PARTITION));
Expand Down
49 changes: 49 additions & 0 deletions warp10/src/main/java/io/warp10/script/functions/LOWERHULL.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
//
// Copyright 2024 SenX S.A.S.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//

package io.warp10.script.functions;

import io.warp10.continuum.gts.GTSHelper;
import io.warp10.continuum.gts.GeoTimeSerie;
import io.warp10.script.GTSStackFunction;
import io.warp10.script.WarpScriptException;
import io.warp10.script.WarpScriptStack;

import java.util.Map;

public class LOWERHULL extends GTSStackFunction {
public LOWERHULL(String name) {
super(name);
}

@Override
protected Object gtsOp(Map<String, Object> params, GeoTimeSerie gts) throws WarpScriptException {
if (0 == gts.size()) {
return gts.clone();
}

if (gts.getType() != GeoTimeSerie.TYPE.DOUBLE && gts.getType() != GeoTimeSerie.TYPE.LONG) {
throw new WarpScriptException(getName() + " expects a numeric GTS.");
}

return GTSHelper.lowerHull(gts);
}

@Override
protected Map<String, Object> retrieveParameters(WarpScriptStack stack) throws WarpScriptException {
return null;
}
}
49 changes: 49 additions & 0 deletions warp10/src/main/java/io/warp10/script/functions/UPPERHULL.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
//
// Copyright 2024 SenX S.A.S.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//

package io.warp10.script.functions;

import io.warp10.continuum.gts.GTSHelper;
import io.warp10.continuum.gts.GeoTimeSerie;
import io.warp10.script.GTSStackFunction;
import io.warp10.script.WarpScriptException;
import io.warp10.script.WarpScriptStack;

import java.util.Map;

public class UPPERHULL extends GTSStackFunction {
public UPPERHULL(String name) {
super(name);
}

@Override
protected Object gtsOp(Map<String, Object> params, GeoTimeSerie gts) throws WarpScriptException {
if (0 == gts.size()) {
return gts.clone();
}

if (gts.getType() != GeoTimeSerie.TYPE.DOUBLE && gts.getType() != GeoTimeSerie.TYPE.LONG) {
throw new WarpScriptException(getName() + " expects a numeric GTS.");
}

return GTSHelper.upperHull(gts);
}

@Override
protected Map<String, Object> retrieveParameters(WarpScriptStack stack) throws WarpScriptException {
return null;
}
}

0 comments on commit 9c39183

Please sign in to comment.