/* * Geotoolkit - An Open Source Java GIS Toolkit * http://www.geotoolkit.org * * (C) 2013, Geomatys * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; * version 2.1 of the License. * * This library 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 * Lesser General Public License for more details. */ package org.geotoolkit.display3d.scene.quadtree; import java.awt.Dimension; import java.awt.Point; import org.apache.sis.geometry.GeneralEnvelope; import org.geotoolkit.factory.FactoryFinder; import org.geotoolkit.math.XMath; import org.apache.sis.referencing.CRS; import org.geotoolkit.referencing.cs.PredefinedCS; import org.apache.sis.internal.system.DefaultFactories; import org.opengis.geometry.Envelope; import org.opengis.parameter.ParameterValueGroup; import org.opengis.referencing.crs.CRSFactory; import org.opengis.referencing.crs.CoordinateReferenceSystem; import org.opengis.referencing.crs.GeographicCRS; import org.opengis.referencing.crs.ProjectedCRS; import org.opengis.referencing.operation.Conversion; import org.opengis.referencing.operation.CoordinateOperationFactory; import org.opengis.referencing.operation.MathTransformFactory; import org.opengis.referencing.operation.OperationMethod; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.logging.Level; import org.geotoolkit.display3d.Map3D; import org.apache.sis.geometry.Envelopes; /** * @author Thomas Rouby (Geomatys)) */ public class QuadTree { private final QuadTreeNode rootNode; private final int minTreeDepth, maxTreeDepth; private final Map3D map; /** * Arbitrary tileSize in pixel of each Tile3D of the node */ private Dimension tileSize = new Dimension(256,256); private Dimension ptsSize = new Dimension(25,25); public QuadTree(Map3D map, Envelope envelope) { this(map, envelope, 1, 12); } public QuadTree(Map3D map, Envelope envelope, int minTreeDepth, int maxTreeDepth) { this.map = map; this.minTreeDepth = Math.max(1,minTreeDepth); this.maxTreeDepth = Math.max(1, maxTreeDepth); final Envelope plateCarre = transformToPlateCarre(envelope); final GeneralEnvelope tmpEnv = (plateCarre instanceof GeneralEnvelope)?((GeneralEnvelope)plateCarre):(new GeneralEnvelope(plateCarre)); final double lonSpan = tmpEnv.getSpan(0); final double latSpan = tmpEnv.getSpan(1); if (lonSpan > latSpan){ tmpEnv.setRange(1, tmpEnv.getMaximum(1)-lonSpan, tmpEnv.getMaximum(1)); } else if (latSpan > lonSpan) { tmpEnv.setRange(0, tmpEnv.getMinimum(0), tmpEnv.getMinimum(0)+latSpan); } rootNode = new QuadTreeNode(map, null, tmpEnv, null); } private Envelope transformToPlateCarre(Envelope env){ try{ final Envelope tmpEnv = Envelopes.transform(env, CRS.getHorizontalComponent(env.getCoordinateReferenceSystem())); if (tmpEnv.getCoordinateReferenceSystem() instanceof GeographicCRS){ final GeographicCRS geoCrs = (GeographicCRS) tmpEnv.getCoordinateReferenceSystem(); final MathTransformFactory mathTransformFactory = FactoryFinder.getMathTransformFactory(null); final ParameterValueGroup plate_carree = mathTransformFactory.getDefaultParameters("Plate_Carree"); final CoordinateOperationFactory coordinateOperationFactory = org.geotoolkit.referencing.CRS.getCoordinateOperationFactory(true); final OperationMethod operationMethod = coordinateOperationFactory.getOperationMethod("Plate_Carree"); final Map<String, Object> params = new HashMap<String, Object>(); params.put("name","plate_carre"); final Conversion createDefiningConversion = coordinateOperationFactory.createDefiningConversion(params, operationMethod, plate_carree); final CRSFactory crsFactory = DefaultFactories.forBuildin(CRSFactory.class); final ProjectedCRS createProjectedCRS = crsFactory.createProjectedCRS(params, geoCrs, createDefiningConversion, PredefinedCS.PROJECTED); return Envelopes.transform(tmpEnv, createProjectedCRS); } } catch (Exception ex) { Map3D.LOGGER.log(Level.WARNING, "", ex); } return env; } public Envelope getGeneralEnvelope() { return rootNode.getEnvelope(); } public CoordinateReferenceSystem getCoordinateReferenceSystem() { return rootNode.getEnvelope().getCoordinateReferenceSystem(); } public QuadTreeNode getRootNode() { return rootNode; } public int getMaxTreeDepth() { return this.maxTreeDepth; } public int getMinTreeDepth() { return this.minTreeDepth; } public QuadTreeNode getOrCreateNode(int treeDepth, int numX, int numY) { if (treeDepth > this.maxTreeDepth || treeDepth < this.minTreeDepth) return null; final Point[] id = QuadTreeUtils.findId(treeDepth, numX, numY); return rootNode.getOrCreateChild(id); } public QuadTreeNode getOrCreateNode(Point[] id) { if (id.length > this.maxTreeDepth || id.length < this.minTreeDepth) return null; return rootNode.getOrCreateChild(id); } public List<QuadTreeNode> findView(int treeDepth, double valX, double valY, double viewDist) { final List<QuadTreeNode> viewList = new ArrayList<>(); searchView(viewList, this.rootNode, XMath.clamp(treeDepth, this.minTreeDepth, this.maxTreeDepth), valX, valY, viewDist); return viewList; } private boolean needSubdivise(QuadTreeNode candidate, int targetDepth, double x, double y, double distTest) { final int testDepth = candidate.getTreeDepth(); if (testDepth >= targetDepth || testDepth > this.maxTreeDepth) return false; if (testDepth < this.minTreeDepth) return true; final Envelope envelope = candidate.getEnvelope(); if (envelope.getMinimum(0) <= x && envelope.getMaximum(0) >= x && envelope.getMinimum(1) <= y && envelope.getMaximum(1) >= y) { return true; } final double rayonX = envelope.getSpan(0)/2.0; final double rayonY = envelope.getSpan(1)/2.0; final double rayon = Math.hypot(rayonX, rayonY); final double distanceX = Math.abs(envelope.getMedian(0)-x); final double distanceY = Math.abs(envelope.getMedian(1)-y); final double distance = Math.hypot(distanceX, distanceY); final double nodeDist = distance - rayon; if(nodeDist<0) return true; final int diff = (int)Math.floor(nodeDist / distTest); return testDepth < targetDepth-diff; } private void searchView(List<QuadTreeNode> viewList, QuadTreeNode node, int treeDepth, double x, double y, double viewDist){ if (node.getTreeDepth() >= treeDepth || node.getTreeDepth() > this.maxTreeDepth){ viewList.add(node); } else { if (needSubdivise(node, treeDepth, x, y, viewDist)){ searchView(viewList, node.getOrCreateChild(new Point(0,0)), treeDepth, x, y, viewDist); searchView(viewList, node.getOrCreateChild(new Point(1,0)), treeDepth, x, y, viewDist); searchView(viewList, node.getOrCreateChild(new Point(0,1)), treeDepth, x, y, viewDist); searchView(viewList, node.getOrCreateChild(new Point(1,1)), treeDepth, x, y, viewDist); } else { viewList.add(node); } } } public Dimension getTileSize() { return tileSize; } public void setTileSize(Dimension tileSize) { this.tileSize = tileSize; } public Dimension getPtsSize() { return ptsSize; } public void setPtsSize(Dimension ptsSize) { this.ptsSize = ptsSize; } @Override public String toString() { return super.toString()+"\n"+rootNode.toString(); } }