Please wait. This can take some minutes ...
Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance.
Project price only 1 $
You can buy this project and download/modify it how often you want.
io.questdb.griffin.engine.functions.groupby.HaversineDistDegreeGroupByFunction Maven / Gradle / Ivy
/*******************************************************************************
* ___ _ ____ ____
* / _ \ _ _ ___ ___| |_| _ \| __ )
* | | | | | | |/ _ \/ __| __| | | | _ \
* | |_| | |_| | __/\__ \ |_| |_| | |_) |
* \__\_\\__,_|\___||___/\__|____/|____/
*
* Copyright (c) 2014-2019 Appsicle
* Copyright (c) 2019-2020 QuestDB
*
* 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.questdb.griffin.engine.functions.groupby;
import io.questdb.cairo.ArrayColumnTypes;
import io.questdb.cairo.ColumnType;
import io.questdb.cairo.map.MapValue;
import io.questdb.cairo.sql.Function;
import io.questdb.cairo.sql.Record;
import io.questdb.griffin.engine.functions.DoubleFunction;
import io.questdb.griffin.engine.functions.GroupByFunction;
import io.questdb.griffin.engine.functions.TernaryFunction;
import io.questdb.std.Numbers;
import org.jetbrains.annotations.NotNull;
import static java.lang.Math.*;
public class HaversineDistDegreeGroupByFunction extends DoubleFunction implements GroupByFunction, TernaryFunction {
private final static double EARTH_RADIUS = 6371.088;
private final Function latDegree;
private final Function lonDegree;
private final Function timestamp;
private int valueIndex;
public HaversineDistDegreeGroupByFunction(int position, @NotNull Function latDegree, @NotNull Function lonDegree, Function timestamp) {
super(position);
this.latDegree = latDegree;
this.lonDegree = lonDegree;
this.timestamp = timestamp;
}
@Override
public double getDouble(Record rec) {
return getDistance(rec);
}
@Override
public Function getLeft() {
return this.latDegree;
}
@Override
public Function getCenter() {
return this.lonDegree;
}
@Override
public Function getRight() {
return this.timestamp;
}
@Override
public void interpolateBoundary(
MapValue value1,
MapValue value2,
long boundaryTimestamp,
boolean isEndOfBoundary
) {
double distance = calculateHaversineDistance(value1, value2);
//interpolate
long ts1 = getLastTimestamp(value1);
long ts2 = getFirstTimestamp(value2);
long boundaryLength = isEndOfBoundary ? boundaryTimestamp - ts1 : ts2 - boundaryTimestamp;
double interpolatedBoundaryDistance = (boundaryLength * distance) / (ts2 - ts1);
//save
MapValue result = isEndOfBoundary ? value1 : value2;
saveDistance(interpolatedBoundaryDistance, result, getDistance(result));
}
@Override
public void interpolateGap(
MapValue result,
MapValue value1,
MapValue value2,
long gapSize
) {
double distance = calculateHaversineDistance(value1, value2);
//interpolate
long ts1 = getLastTimestamp(value1);
long ts2 = getFirstTimestamp(value2);
double interpolatedGapDistance = (gapSize * distance) / (ts2 - ts1);
saveDistance(result, interpolatedGapDistance);
}
@Override
public boolean isScalar() {
return false;
}
@Override
public void computeFirst(MapValue mapValue, Record record) {
//first item
saveFirstItem(mapValue, this.latDegree.getDouble(record), this.lonDegree.getDouble(record), this.timestamp.getTimestamp(record));
//last item
saveLastItem(mapValue, this.latDegree.getDouble(record), this.lonDegree.getDouble(record), this.timestamp.getTimestamp(record));
//result
saveDistance(mapValue, 0);
}
@Override
public void computeNext(MapValue mapValue, Record record) {
double lat1Degrees = getLastLatitude(mapValue);
double lon1Degrees = getLastLongitude(mapValue);
long timestamp1 = getLastTimestamp(mapValue);
double lat2Degrees = this.latDegree.getDouble(record);
double lon2Degrees = this.lonDegree.getDouble(record);
long timestamp2 = this.timestamp.getTimestamp(record);
if (!Double.isNaN(lat1Degrees) && !Double.isNaN(lon1Degrees) && timestamp1 != Numbers.LONG_NaN) {
if (!Double.isNaN(lat2Degrees) && !Double.isNaN(lon2Degrees) && timestamp2 != Numbers.LONG_NaN) {
double currentTotalDistance = getDistance(mapValue);
double distance = calculateHaversineDistanceFromDegrees(lat1Degrees, lon1Degrees, lat2Degrees, lon2Degrees, currentTotalDistance);
saveLastItem(mapValue, lat2Degrees, lon2Degrees, timestamp2);
saveDistance(mapValue, distance);
}
} else {
saveLastItem(mapValue, lat2Degrees, lon2Degrees, timestamp2);
}
}
@Override
public void pushValueTypes(ArrayColumnTypes columnTypes) {
this.valueIndex = columnTypes.getColumnCount();
//first item
columnTypes.add(ColumnType.DOUBLE);
columnTypes.add(ColumnType.DOUBLE);
columnTypes.add(ColumnType.LONG);
//last item
columnTypes.add(ColumnType.DOUBLE);
columnTypes.add(ColumnType.DOUBLE);
columnTypes.add(ColumnType.LONG);
//result
columnTypes.add(ColumnType.DOUBLE);
}
@Override
public void setNull(MapValue mapValue) {
//set null to first item
saveFirstItem(mapValue, Double.NaN, Double.NaN, Numbers.LONG_NaN);
//set null to last item
saveLastItem(mapValue, Double.NaN, Double.NaN, Numbers.LONG_NaN);
//
saveDistance(mapValue, 0.0);
}
private double calculateHaversineDistance(MapValue value1, MapValue value2) {
//value1
double lat1Degrees = getLastLatitude(value1);
double lon1Degrees = getLastLongitude(value1);
//value2
double lat2Degrees = getFirstLatitude(value2);
double lon2Degrees = getFirstLongitude(value2);
return calculateHaversineDistanceFromDegrees(lat1Degrees, lon1Degrees, lat2Degrees, lon2Degrees, 0);
}
private double calculateHaversineDistanceFromDegrees(double lat1Degrees, double lon1Degrees, double lat2Degrees, double lon2Degrees, double currentTotalDistance) {
double lat1 = toRad(lat1Degrees);
double lon1 = toRad(lon1Degrees);
double lat2 = toRad(lat2Degrees);
double lon2 = toRad(lon2Degrees);
return calculateHaversineDistanceFromRadians(currentTotalDistance, lat1, lon1, lat2, lon2);
}
private double calculateHaversineDistanceFromRadians(double currentTotal, double lat1, double lon1, double lat2, double lon2) {
double halfLatDist = (lat2 - lat1) / 2;
double halfLonDist = (lon2 - lon1) / 2;
double a = sin(halfLatDist) * sin(halfLatDist) + cos(lat1) * cos(lat2) * sin(halfLonDist) * sin(halfLonDist);
double c = 2 * atan2(sqrt(a), sqrt(1 - a));
currentTotal += EARTH_RADIUS * c;
return currentTotal;
}
private double getDistance(Record result) {
return result.getDouble(valueIndex + 6);
}
private double toRad(double deg) {
return deg * PI / 180;
}
private double getFirstLatitude(MapValue value) {
return value.getDouble(valueIndex);
}
private double getFirstLongitude(MapValue value) {
return value.getDouble(valueIndex + 1);
}
private long getFirstTimestamp(MapValue value) {
return value.getTimestamp(valueIndex + 2);
}
private double getLastLatitude(MapValue value1) {
return value1.getDouble(valueIndex + 3);
}
private double getLastLongitude(MapValue mapValue) {
return mapValue.getDouble(valueIndex + 4);
}
private long getLastTimestamp(MapValue value) {
return value.getTimestamp(valueIndex + 5);
}
private void saveDistance(double interpolatedBoundaryDistance, MapValue result, double currentDistance) {
saveDistance(result, currentDistance + interpolatedBoundaryDistance);
}
private void saveDistance(MapValue result, double distance) {
result.putDouble(this.valueIndex + 6, distance);
}
private void saveFirstItem(MapValue mapValue, double lat, double lon, long timestamp) {
mapValue.putDouble(this.valueIndex, lat);
mapValue.putDouble(this.valueIndex + 1, lon);
mapValue.putTimestamp(this.valueIndex + 2, timestamp);
}
private void saveLastItem(MapValue mapValue, double lat, double lon, long timestamp) {
mapValue.putDouble(this.valueIndex + 3, lat);
mapValue.putDouble(this.valueIndex + 4, lon);
mapValue.putTimestamp(this.valueIndex + 5, timestamp);
}
}