Subversion Repositories mkgmap

Rev

Rev 3605 | Blame | Compare with Previous | Last modification | View Log | RSS feed

/*
 * Copyright (C) 2014 Gerd Petermann
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License version 3 or
 * version 2 as published by the Free Software Foundation.
 *
 * 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.
 */

package uk.me.parabola.util;


import java.util.LinkedHashSet;
import java.util.Set;
import uk.me.parabola.imgfmt.app.Coord;


/**
 * A kd-tree (2D) implementation to solve the nearest neighbor problem.
 * The tree is not explicitly balanced.
 *
 * @author Gerd Petermann
 *
 */

public class KdTree <T extends Locatable> {
        private static final boolean ROOT_NODE_USES_LONGITUDE = false;
       
        private class KdNode {
                T point;
                KdNode left;
                KdNode right;

                KdNode(T p) {
                        point = p;
                }
        }
        // the tree root
    private KdNode root;
    // number of saved objects  
    private int size;

    // helpers
    private T nextPoint ;
    private double minDist;
    private double maxDist;
    private Set<T> set;

    /**
     *  create an empty tree
     */

        public KdTree() {
                root = null;
        }

        public long size()
        {
                return size;
        }

       
        /**
         * Start the add action with the root
         * @param toAdd
         */

        public void add(T toAdd) {
                size++;
                root = add(toAdd, root, ROOT_NODE_USES_LONGITUDE);
        }

        /**
         * Compares the given axis of both points.
         * @param longitude <code>true</code>: compare longitude; <code>false</code> compare latitude
         * @param c1 a point
         * @param c2 another point
         * @return <code>true</code> the axis value of c1 is smaller than c2;
         *              <code>false</code> the axis value of c1 is equal or larger than c2
         */

        private boolean isSmaller(boolean longitude, Coord c1, Coord c2) {
                if (longitude) {
                        return c1.getLongitude() < c2.getLongitude();
                } else {
                        return c1.getLatitude() < c2.getLatitude();
                }
        }
       
        /**
         * Recursive routine to find the right place for inserting
         * into the tree.  
         * @param toAdd the point
         * @param tree the subtree root node where to add (maybe <code>null</code>)
         * @param useLongitude <code>true</code> the tree node uses longitude for comparison;
         *              <code>false</code> the tree node uses latitude for comparison
         * @return the subtree root node after insertion
         */

    private KdNode add( T toAdd, KdNode tree,  boolean useLongitude){
        if( tree == null ) {
            tree = new KdNode( toAdd );
        } else {
                if(isSmaller(useLongitude, toAdd.getLocation(), tree.point.getLocation())) {
                        tree.left = add(toAdd, tree.left, !useLongitude);
                } else {
                        tree.right = add(toAdd, tree.right, !useLongitude);
                }
        }
        return tree;
    }
   
        /**
         * Searches for the point that has smallest distance to the given point.
         * @param p the point to search for
         * @return the point with shortest distance to <var>p</var>
         */

        public T findNextPoint(Locatable p) {
                // reset
                minDist = Double.MAX_VALUE;
                maxDist = -1;
                set = null;
                nextPoint = null;
               
                // false => first node is a latitude level
                return findNextPoint(p, root, ROOT_NODE_USES_LONGITUDE);
        }

        /**
         * Searches for the point that has smallest distance to the given point.
         * @param p the point to search for
         * @return the point with shortest distance to <var>p</var>
         */

        public Set<T> findNextPoint(Locatable p, double maxDist) {
                // reset
                minDist = Double.MAX_VALUE;
                this.maxDist = Math.pow(maxDist * 360 / Coord.U, 2); // convert maxDist in meter to distanceInDegreesSquared
                nextPoint = null;
                this.set = new LinkedHashSet<>();
                // false => first node is a latitude level
                findNextPoint(p, root, ROOT_NODE_USES_LONGITUDE);
                return set;
        }

        private T findNextPoint(Locatable p, KdNode tree, boolean useLongitude) {
                boolean continueWithLeft = false;
                if (tree == null)
                        return nextPoint;
               
                if (tree.left == null && tree.right == null){
                        double dist = tree.point.getLocation().distanceInDegreesSquared(p.getLocation());
                        if (dist <= maxDist && set != null){
                                set.add(tree.point);
                        }
                        if (dist < minDist){
                                nextPoint = tree.point;
                                if (dist < maxDist)
                                        minDist = maxDist;
                                else
                                        minDist = dist;
                        }
                        return nextPoint;
                }
                else {
                        if (isSmaller(useLongitude, p.getLocation(), tree.point.getLocation())){
                                continueWithLeft = false;
                                nextPoint = findNextPoint(p, tree.left, !useLongitude);
                        }
                        else {
                                continueWithLeft = true;
                                nextPoint = findNextPoint(p, tree.right, !useLongitude);
                        }
                }
               
                double dist = tree.point.getLocation().distanceInDegreesSquared(p.getLocation());
                if (dist <= maxDist && set != null)
                        set.add(tree.point);
                if (dist < minDist){
                        nextPoint = tree.point;
                        minDist = dist;
                        if (dist < maxDist)
                                minDist = maxDist;
                        else
                                minDist = dist;
                }              
                // do we have to search the other part of the tree?
                Coord test;
                if (useLongitude)
                        test = Coord.makeHighPrecCoord(p.getLocation().getHighPrecLat(), tree.point.getLocation().getHighPrecLon());
                else
                        test = Coord.makeHighPrecCoord(tree.point.getLocation().getHighPrecLat(), p.getLocation().getHighPrecLon());
                if (test.distanceInDegreesSquared(p.getLocation()) < minDist){
                        if (continueWithLeft)
                                nextPoint = findNextPoint(p, tree.left, !useLongitude);
                        else
                                nextPoint = findNextPoint(p, tree.right, !useLongitude);
                }
                return nextPoint;
        }
}