All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.neo4j.kernel.impl.index.schema.PointKey Maven / Gradle / Ivy

Go to download

Neo4j kernel is a lightweight, embedded Java database designed to store data structured as graphs rather than tables. For more information, see http://neo4j.org.

There is a newer version: 5.25.1
Show newest version
/*
 * Copyright (c) "Neo4j"
 * Neo4j Sweden AB [http://neo4j.com]
 *
 * This file is part of Neo4j.
 *
 * Neo4j is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see .
 */
package org.neo4j.kernel.impl.index.schema;

import org.neo4j.gis.spatial.index.curves.SpaceFillingCurve;
import org.neo4j.io.pagecache.PageCursor;
import org.neo4j.kernel.impl.index.schema.config.IndexSpecificSpaceFillingCurveSettings;
import org.neo4j.values.storable.CoordinateReferenceSystem;
import org.neo4j.values.storable.PointValue;
import org.neo4j.values.storable.Value;
import org.neo4j.values.storable.ValueGroup;
import org.neo4j.values.storable.Values;

import static org.neo4j.kernel.impl.index.schema.NativeIndexKey.Inclusion.HIGH;
import static org.neo4j.kernel.impl.index.schema.NativeIndexKey.Inclusion.LOW;
import static org.neo4j.kernel.impl.index.schema.NativeIndexKey.Inclusion.NEUTRAL;

class PointKey extends NativeIndexKey
{
    private static final long[] NO_COORDINATE = new long[0];

    private final IndexSpecificSpaceFillingCurveSettings settings;

    private int crsTableId;
    private int crsCode;
    private long derivedSpaceFillingCurveValue;
    private long[] coordinate;
    private Inclusion inclusion;

    PointKey( IndexSpecificSpaceFillingCurveSettings settings )
    {
        this.settings = settings;
    }

    @Override
    void writeValue( int stateSlot, Value value, Inclusion inclusion )
    {
        // we can cast without check, because the parent (who is the only production user) calls assertValidValue before calling this method
        PointValue pointValue = (PointValue) value;
        CoordinateReferenceSystem crs = pointValue.getCoordinateReferenceSystem();
        crsCode = crs.getCode();
        crsTableId = crs.getTable().getTableId();

        double[] doubleCoordinate = pointValue.coordinate();
        SpaceFillingCurve spaceFillingCurve = settings.forCrs( crsTableId, crsCode );
        derivedSpaceFillingCurveValue = spaceFillingCurve.derivedValueFor( doubleCoordinate );

        coordinate = new long[doubleCoordinate.length];
        for ( int i = 0; i < doubleCoordinate.length; i++ )
        {
            coordinate[i] = Double.doubleToLongBits( doubleCoordinate[i] );
        }

        this.inclusion = inclusion;
    }

    void writePointDerived( CoordinateReferenceSystem crs, long derivedSpaceFillingCurveValue, NativeIndexKey.Inclusion inclusion )
    {
        crsCode = crs.getCode();
        crsTableId = crs.getTable().getTableId();
        this.derivedSpaceFillingCurveValue = derivedSpaceFillingCurveValue;
        coordinate = NO_COORDINATE;
        this.inclusion = inclusion;
    }

    @Override
    void assertValidValue( int stateSlot, Value value )
    {
        if ( !(value instanceof PointValue) )
        {
            throw new IllegalArgumentException( "Unsupported value type: " + value );
        }
    }

    @Override
    Value[] asValues()
    {
        CoordinateReferenceSystem crs = CoordinateReferenceSystem.get( crsTableId, crsCode );
        double[] doubleCoordinate = new double[coordinate.length];
        for ( int i = 0; i < coordinate.length; i++ )
        {
            doubleCoordinate[i] = Double.longBitsToDouble( coordinate[i] );
        }
        return new Value[]{Values.pointValue( crs, doubleCoordinate )};
    }

    @Override
    void initValueAsLowest( int stateSlot, ValueGroup valueGroup )
    {
        // Since table ID is the first thing that is compared,
        // using table ID that is lower than any possible real value
        // (currently the only possible values are 0, 1, 2)
        // is the simplest way how to create the lowest key.
        crsTableId = Integer.MIN_VALUE;
        crsCode = 0;
        derivedSpaceFillingCurveValue = 0;
        coordinate = NO_COORDINATE;
        inclusion = LOW;
    }

    @Override
    void initValueAsHighest( int stateSlot, ValueGroup valueGroup )
    {
        // Since table ID is the first thing that is compared,
        // using table ID that is higher than any possible real value
        // (currently the only possible values are 0, 1, 2)
        // is the simplest way how to create the highest key.
        crsTableId = Integer.MAX_VALUE;
        crsCode = 0;
        derivedSpaceFillingCurveValue = 0;
        coordinate = NO_COORDINATE;
        inclusion = HIGH;
    }

    @Override
    int numberOfStateSlots()
    {
        return 1;
    }

    @Override
    int compareValueTo( PointKey other )
    {
        int tableIdComparison = Integer.compare( this.crsTableId, other.crsTableId );
        if ( tableIdComparison != 0 )
        {
            return tableIdComparison;
        }

        int codeComparison = Integer.compare( this.crsCode, other.crsCode );
        if ( codeComparison != 0 )
        {
            return codeComparison;
        }

        int derivedSpaceFillingCurveValueComparison = Long.compare( this.derivedSpaceFillingCurveValue, other.derivedSpaceFillingCurveValue );
        if ( derivedSpaceFillingCurveValueComparison != 0 )
        {
            return derivedSpaceFillingCurveValueComparison;
        }

        // When we construct spatial sub-queries we create keys with only derived
        // space filling curve value and no actual coordinates. That's why we need
        // to check min dimensions here. In all other cases the number of dimensions
        // for a point is always given by the crs.
        int dimensions = Math.min( this.coordinate.length, other.coordinate.length );
        for ( int i = 0; i < dimensions; i++ )
        {
            // It's ok to compare the coordinate value here without deserializing them
            // because we are only defining SOME deterministic order so that we can
            // correctly separate unique points from each other, even if they collide
            // on the space filling curve.
            int coordinateComparison = Long.compare( this.coordinate[i], other.coordinate[i] );
            if ( coordinateComparison != 0 )
            {
                return coordinateComparison;
            }
        }

        return inclusion.compareTo( other.inclusion );
    }

    void copyFrom( PointKey from )
    {
        setEntityId( from.getEntityId() );
        setCompareId( from.getCompareId() );
        this.inclusion = from.inclusion;
        this.crsTableId = from.crsTableId;
        this.crsCode = from.crsCode;
        this.derivedSpaceFillingCurveValue = from.derivedSpaceFillingCurveValue;
        this.coordinate = new long[from.coordinate.length];
        System.arraycopy( from.coordinate, 0, this.coordinate, 0, from.coordinate.length );
    }

    int size()
    {
        // NOTE: since this index supports only one type, the type information does not have to be stored
        int coordinatesSize = PointKeyUtil.SIZE_GEOMETRY_COORDINATE * coordinate.length;
        return ENTITY_ID_SIZE + PointKeyUtil.SIZE_GEOMETRY_HEADER + PointKeyUtil.SIZE_GEOMETRY_DERIVED_SPACE_FILLING_CURVE_VALUE + coordinatesSize;
    }

    void writeToCursor( PageCursor cursor )
    {
        cursor.putLong( getEntityId() );
        writePointMetadataToCursor( cursor );
        writePointValueToCursor( cursor );
    }

    private void writePointMetadataToCursor( PageCursor cursor )
    {
        PointKeyUtil.writeHeader( cursor, crsTableId, crsCode, coordinate.length );
    }

    private void writePointValueToCursor( PageCursor cursor )
    {
        cursor.putLong( derivedSpaceFillingCurveValue );
        for ( int i = 0; i < coordinate.length; i++ )
        {
            cursor.putLong( coordinate[i] );
        }
    }

    void readFromCursor( PageCursor cursor, int size )
    {
        initialize( cursor.getLong() );
        int dimensions = readPointMetadataFromCursor( cursor );
        coordinate = new long[dimensions];
        readPointValueFromCursor( cursor );
        inclusion = NEUTRAL;

        if ( size() != size )
        {
            cursor.setCursorException( "Failed to read PointKey, because the expected size does not correspond to the stored key" );
        }
    }

    private int readPointMetadataFromCursor( PageCursor cursor )
    {
        int header = PointKeyUtil.readHeader( cursor );
        crsTableId = PointKeyUtil.crsTableId( header );
        crsCode = PointKeyUtil.crsCode( header );
        return PointKeyUtil.dimensions( header );
    }

    private void readPointValueFromCursor( PageCursor cursor )
    {
        derivedSpaceFillingCurveValue = cursor.getLong();
        for ( int i = 0; i < coordinate.length; i++ )
        {
            coordinate[i] = cursor.getLong();
        }
    }

    @Override
    public String toString()
    {
        return "[" + asValues()[0].toString() + "],entityId=" + getEntityId();
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy