logo

6 дек. 2012 г.

Spatial: диаграмма Вороного (Java)

Всем привет!
Сегодня хочу описать решение по генерации диаграммы Вороного с помощью java.

Диаграмма Вороного конечного множества точек S на плоскости представляет такое разбиение плоскости, при котором каждая область этого разбиения образует множество точек, более близких к одному из элементов множества S, чем к любому другому элементу множества.


У диаграммы очень много областей применения, особенно при решении spatial-задач.
/*Например, я с помощью диаграммы Вороного решал задачу кластеризации на карте Санкт-Петербурга */
К сожалению, в Oracle Spatial нет эффективного механизма (я ошибаюсь?) получения данной диаграммы. Да и к тому же Spatial - платная опция...
Поэтому, возможно, приведенное далее решение будет вам интересно!


В решение используется алгоритм от Steven Fortune - "метод заметающей прямой".


Само решение большей частью взято отсюда - http://stackoverflow.com/questions/2346148/fastest-way-to-get-the-set-of-convex-polygons-formed-by-voronoi-line-segments

И доработано таким образом, что при входных данных:
1) набор гео-точек (например, дома города);
2) ограничивающий точки полигон (например, административная граница города)

- возвращается набор полигонов, представляющих собой ячейки диаграммы Вороного.
Причем ячейки Вороного "обрезаны" ограничивающим полигоном.

В качестве примера рассмотрим формирования ячеек Вороного для Василеостровского района Санкт-Петербурга.

1) Скачаем свежие shape-файлы по Санкт-Петербургу

2) Скачиваем - если вы еще не обзавелись - Oracle Mapbuilder

3) Запускаем Mapbuilder
(указываем явно кодировку, чтобы не "потерять" кириллические символы)
java -Dfile.encoding=UTF8 -XX:MaxPermSize=512m -Xmx1024m -jar mapbuilder.jar

4) Создаем новую схему в БД для spatial-тестов
CREATE USER GEO IDENTIFIED BY GEO DEFAULT TABLESPACE USERS QUOTA UNLIMITED ON USERS;

GRANT CONNECT TO GEO;
GRANT RESOURCE TO GEO;

5) Импортируем 2 shape-файла в БД (аналогично этому) - boundary-polygon.shp и poi-point.shp в таблицы D_ZONE_POLY_8307 и D_POI_POINT_8307 соответственно.

6) Меняем у загруженных гео-данных в сферическую систему координат (возможно, вы в дальнейшем будете выводить эти данные в Mapviewer/OBIEE11g)
create table D_ZONE_POLY_3785 as select * from D_ZONE_POLY_8307;
update D_ZONE_POLY_3785 set geometry = sdo_cs.transform(geometry,'USE_SPHERICAL', 3785);
commit;

delete from user_sdo_geom_metadata where table_name = 'D_ZONE_POLY_3785';
insert into user_sdo_geom_metadata
values('D_ZONE_POLY_3785', 'GEOMETRY', sdo_dim_array(sdo_dim_element('X', -20037508.3427, 20037508.3427, 0.05), sdo_dim_element('Y', -20037508.3427, 20037508.3427, 0.05)), 3785);

create index D_ZONE_POLY_3785_sidx on D_ZONE_POLY_3785(GEOMETRY) indextype is mdsys.spatial_index;

create table D_POI_POINT_3785 as select * from D_POI_POINT_8307;
update D_POI_POINT_3785 set geometry = sdo_cs.transform(geometry,'USE_SPHERICAL', 3785);
commit;

delete from user_sdo_geom_metadata where table_name = 'D_POI_POINT_3785';
insert into user_sdo_geom_metadata
values('D_POI_POINT_3785', 'GEOMETRY', sdo_dim_array(sdo_dim_element('X', -20037508.3427, 20037508.3427, 0.05), sdo_dim_element('Y', -20037508.3427, 20037508.3427, 0.05)), 3785);

create index D_POI_POINT_3785_sidx on D_POI_POINT_3785(GEOMETRY) indextype is mdsys.spatial_index;


7) Вычленяем из загруженных данных часть для проведения теста.
Тестовая выборка будет содержать полигон административной границы Василеостровского района Санкт-Петербурга и его дома.
create table D_ZONE_POLY_TEST as
select * from D_ZONE_POLY_3785 t
where name = 'Василеостровский район';

delete from user_sdo_geom_metadata where table_name = 'D_ZONE_POLY_TEST';
insert into user_sdo_geom_metadata
values('D_ZONE_POLY_TEST', 'GEOMETRY', sdo_dim_array(sdo_dim_element('X', -20037508.3427, 20037508.3427, 0.05), sdo_dim_element('Y', -20037508.3427, 20037508.3427, 0.05)), 3785);

create index D_ZONE_POLY_TEST_sidx on D_ZONE_POLY_TEST(GEOMETRY) indextype is mdsys.spatial_index;

В таблицу D_POI_POINT_TEST сохраняем лишь дома, принадлежащие выбранному району города.
create table D_POI_POINT_TEST as
select p.*
  from D_POI_POINT_3785 p, D_ZONE_POLY_TEST z
 where SDO_CONTAINS(z.geometry, p.geometry) = 'TRUE';
 
delete from user_sdo_geom_metadata where table_name = 'D_POI_POINT_TEST';
insert into user_sdo_geom_metadata
values('D_POI_POINT_TEST', 'GEOMETRY', sdo_dim_array(sdo_dim_element('X', -20037508.3427, 20037508.3427, 0.05), sdo_dim_element('Y', -20037508.3427, 20037508.3427, 0.05)), 3785);

create index D_POI_POINT_TEST_sidx on D_POI_POINT_TEST(GEOMETRY) indextype is mdsys.spatial_index; 

8) Создадим таблицу для хранения результатов теста
create table D_VORONOI_POLY(GEOMETRY MDSYS.SDO_GEOMETRY, OSM_ID NUMBER, NEIGHBORS VARCHAR2(4000));

delete from user_sdo_geom_metadata where table_name = 'D_VORONOI_POLY';
insert into user_sdo_geom_metadata
values('D_VORONOI_POLY', 'GEOMETRY', sdo_dim_array(sdo_dim_element('X', -20037508.3427, 20037508.3427, 0.05), sdo_dim_element('Y', -20037508.3427, 20037508.3427, 0.05)), 3785);

create index D_VORONOI_POLY_sidx on D_VORONOI_POLY(GEOMETRY) indextype is mdsys.spatial_index; 

9) Скачаем архив с java-проектом генерации диаграммы Вороного.
В главном файле проекта - src\ru\servplus\geom\Voronoi.java - следует изменить настройки JDBC-доступа к тестовой схеме БД.
package ru.servplus.geom;

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.ListIterator;
import java.util.PriorityQueue;
import java.util.TreeSet;

import oracle.jdbc.*;
import oracle.spatial.geometry.JGeometry;

import com.vividsolutions.jts.geom.Coordinate;
import com.vividsolutions.jts.geom.Geometry;
import com.vividsolutions.jts.geom.GeometryFactory;
import com.vividsolutions.jts.geom.LineString;
import com.vividsolutions.jts.geom.LinearRing;



public class Voronoi {
 boolean debug = false;
 
 Geometry zonePoly;
 GeometryFactory geometryFactory;
 
 double xmin;
 double ymin;
 double xmax;
 double ymax;
 double width;
 double height;
 
 Point topleft;
    Point topright;
    Point bottomleft;
    Point bottomright;  
 
 Connection conn = null;
 
    // The set of points that control the centers of the cells
    private LinkedList<Point> pts;
    // A list of line segments that defines where the cells are divided
    private LinkedList<Edge> output;
    // Special list of points that are redundant and need to be removed in post processing
    private LinkedList<Point> mechs;
    // The sites that have not yet been processed, in acending order of X coordinate
    private PriorityQueue<Point> sites;
    // Possible upcoming cirlce events in acending order of X coordinate
    private PriorityQueue<CircleEvent> events;
    // The root of the binary search tree of the parabolic wave front
    private Arc root;
       
    
 public static void main(String[] args) {  
  System.out.println("start!");
  try {
   Voronoi vor = new Voronoi();
   vor.execute();
  }
     catch(Exception e)
     {
      e.printStackTrace();
     }

  System.out.println("end!");        
 }    
    
    
 public Voronoi() throws Exception{
        
  //Init the lists
        pts = new LinkedList<Point>();
        output = new LinkedList<Edge>();
        mechs = new LinkedList<Point>();
        sites = new PriorityQueue<Point>(100);
        events = new PriorityQueue<CircleEvent>(100);
        
  JGeometry geom = null;
  PreparedStatement pstmt = null;
  ResultSet rs = null;
  oracle.sql.STRUCT st = null; 
  Point p;
  int key;
  
     String jdbcUrl = "jdbc:oracle:thin:@localhost:1521:ORCL";
     String jdbcUsr = "GEO";
     String jdbcPwd = "GEO";
      
     Class.forName ("oracle.jdbc.OracleDriver").newInstance();
     this.conn = DriverManager.getConnection(jdbcUrl, jdbcUsr, jdbcPwd);
  
     if(conn == null)
     {
       System.out.print("cannot get connection!");
     }
     
     //calculate zone MBR
     pstmt = conn.prepareStatement(
       "select max(decode(t.id, 1, t.x, null)) x_min, "+
          "       max(decode(t.id, 2, t.x, null)) x_max, "+
          "       max(decode(t.id, 1, t.y, null)) y_min, "+
          "       max(decode(t.id, 2, t.y, null)) y_max  "+
          "    from TABLE (select sdo_util.getvertices(SDO_GEOM.SDO_MBR(geometry)) from D_ZONE_POLY_TEST) t");
     
     rs = pstmt.executeQuery(); 
     while (rs.next()){
      xmin = rs.getDouble("X_MIN") - 1000;
      ymin = rs.getDouble("Y_MIN") - 1000;
      xmax = rs.getDouble("X_MAX") + 1000;
      ymax = rs.getDouble("Y_MAX") + 1000;
     }          
     height = ymax - ymin;
  width = xmax - xmin;     
  pstmt.close();  
  
     topleft = new Point(xmin,ymin);
        topright = new Point(xmax,ymin);
        bottomleft = new Point(xmin,ymax);
        bottomright = new Point(xmax,ymax);  
  
  //get zone Points
     pstmt = conn.prepareStatement(
       "select OSM_ID, GEOMETRY " +
       "from D_POI_POINT_TEST " +
       "where 1=1 ");
     rs = pstmt.executeQuery(); 
          
     while (rs.next())
     {
      st = (oracle.sql.STRUCT) rs.getObject("GEOMETRY");
      key = rs.getInt("OSM_ID");
      geom = JGeometry.load(st);
      double[] coords = geom.getPoint(); 
      p = new Point(coords[0],coords[1]);
      p.key = key;            
            pts.add(p);  
     }         
     pstmt.close();
        
     ListIterator<Point> points = pts.listIterator(0);
    
  //get zone polygon
  geometryFactory = new GeometryFactory();
  
     pstmt = conn.prepareStatement("select GEOMETRY from D_ZONE_POLY_TEST ");
     rs = pstmt.executeQuery(); 

     zonePoly = null;
     LinearRing zoneLr;
     while (rs.next())
     {
      st = (oracle.sql.STRUCT) rs.getObject("GEOMETRY");
      geom = JGeometry.load(st);
      Coordinate[] coords = new Coordinate[geom.getNumPoints()];

      for (int i=0; i<geom.getNumPoints(); i++){
       coords[i] = new Coordinate(geom.getOrdinatesArray()[i*2],geom.getOrdinatesArray()[i*2 + 1]);
      } 
      
      zoneLr = geometryFactory.createLinearRing(coords);
      zonePoly = geometryFactory.createPolygon(zoneLr, null);
     }   
     pstmt.close();
 }

 
 public void execute() throws Exception{

  JGeometry geom = null;
  PreparedStatement pstmt = null;
  //ResultSet rs = null;
  oracle.sql.STRUCT st = null;  
  Point p;
  
  if(conn == null)
     {
         System.out.print("cannot get connection!");
     } 

        // run fortune's algorithm to get new line segments
        runFortune(pts);            
        
        APoint v,ov,frst;
        Point n;
        StringBuffer neighborsConcat;
        String neighborsConcatStr = null;
        ListIterator<Point> siteit = pts.listIterator(0);
        
        pstmt = conn.prepareStatement("DELETE FROM D_VORONOI_POLY");
        pstmt.execute(); 
        
        pstmt = conn.prepareStatement("INSERT INTO D_VORONOI_POLY(GEOMETRY, OSM_ID, NEIGHBORS) VALUES(?,?,?)");
        
        ((OraclePreparedStatement)pstmt).setExecuteBatch (pts.size());
        
        while (siteit.hasNext()) {
            p = siteit.next();
            
            if (p.sverts != null){
             Iterator<APoint> vert = p.sverts.iterator();
             if (vert.hasNext()){
              v = vert.next();
              
              double[] ring = new double[(p.sverts.size() + 1) * 2];
              int f = 0;
              frst = v;
              
              ring[f++] = frst.x;
           ring[f++] = frst.y;
              
              while (vert.hasNext()) {
               ov = v;
               v = vert.next();
               
               ring[f++] = v.x;
               ring[f++] = v.y;
              }
              
              ring[f++] = frst.x;
           ring[f++] = frst.y;
              
              Coordinate[] coords = new Coordinate[ring.length/2];

              for (int i=0; i<ring.length/2; i++){
               coords[i] = new Coordinate(ring[i*2],ring[i*2 + 1]);
              } 
              
              LinearRing cellLr = geometryFactory.createLinearRing(coords);
              Geometry cellPoly = geometryFactory.createPolygon(cellLr, null);           
           
              Geometry cellIntersect = zonePoly.intersection(cellPoly);

              if (cellIntersect != null) {
                               
               Object[] intersectRings = new Object[cellIntersect.getNumGeometries()];
               for(int zz=0; zz<cellIntersect.getNumGeometries(); zz++) {
                
                Coordinate[] insersectCoord = cellIntersect.getGeometryN(zz).getCoordinates();
                   double[] intersectRing = new double[insersectCoord.length * 2];
                   for (int i=0; i< insersectCoord.length;i++){
                    intersectRing[i*2] = insersectCoord[i].x;
                    intersectRing[i*2+1] = insersectCoord[i].y;
                   }
                
                   intersectRings[zz] = intersectRing;
               }               
               
               try {
       geom = JGeometry.createLinearPolygon(intersectRings, 2, 3785);
              
       if ( intersectRings.length > 1 && geom.getType() == 3 ) {
        geom.setType( JGeometry.GTYPE_MULTIPOLYGON );
        //System.out.println("GTYPE_MULTIPOLYGON - "+p.key); 
       }
       
       st = JGeometry.store(geom, conn);
       
       pstmt.setObject(1, st);
       pstmt.setObject(2, p.key);
       
       
      } catch (RuntimeException e) {
       e.printStackTrace();
       System.out.println(p.key);       
      }
                     
                     neighborsConcat = new StringBuffer();                     
                     
                     Iterator<Point> neighbors = p.neighbors.iterator();

                     while (neighbors.hasNext()){
                      n = neighbors.next();
                      neighborsConcat.append(n.key+",");
                     }
                     if (neighborsConcat.length() > 0)
                      neighborsConcatStr = neighborsConcat.substring(0, neighborsConcat.length()-1);
                     pstmt.setObject(3, neighborsConcatStr);

                     pstmt.executeUpdate();                                 
              }               
             }
            }            
        }    
        
        ((OraclePreparedStatement)pstmt).sendBatch(); // JDBC sends the queued request

        conn.commit();

        pstmt.close();             
 }


    void runFortune(LinkedList<Point> pts) {

     sites.clear();
     events.clear();
     output.clear();
     mechs.clear();
     root = null;

     ListIterator<Point> i = pts.listIterator(0);
     Point meh;
     while (i.hasNext()) {
      meh = i.next();
      sites.offer(meh);
      if (meh.sverts == null)
       meh.sverts = new TreeSet<APoint>();    
      meh.sverts.clear();
      if (meh.neighbors == null)
       meh.neighbors = new TreeSet<Point>();    
      meh.neighbors.clear();
     }

     // Process the queues; select the top element with smaller x coordinate.
     while (sites.size() > 0) {
      if ((events.size() > 0) && ((events.peek().xpos) <= (sites.peek().x))) {
       processCircleEvent(events.poll());
      } else {
       //process a site event by adding a curve to the parabolic front
       frontInsert(sites.poll());
      }
     }

     // After all points are processed, do the remaining circle events.
     while (events.size() > 0) {
      processCircleEvent(events.poll());
     }

     // Clean up dangling edges.
     finishEdges();

     //make polygons from edges
     polyFinish();
    }

    private void processCircleEvent(CircleEvent event) {
        if (event.valid) {
         Arc parc = event.a;
         
            //start a new edge
            Edge edgy = new Edge(event.p, parc.prev.focus, parc.next.focus);
            
            // Remove the associated arc from the front;
            if (parc.prev != null) {
                parc.prev.next = parc.next;
                parc.prev.edge1 = edgy;
            }
            if (parc.next != null) {
                parc.next.prev = parc.prev;
                parc.next.edge0 = edgy;
            }
            
            // Finish the edges before and after this arc.
            if (parc.edge0 != null) {
             parc.edge0.finish(event.p);
            }
            if (parc.edge1 != null) {
             parc.edge1.finish(event.p);
            }

            // Recheck circle events on either side of p:
            if (parc.prev != null)
                checkCircleEvent(parc.prev, event.xpos);
            if (parc.next != null)
                checkCircleEvent(parc.next, event.xpos);

        }
    }

    void frontInsert(Point focus) {
        if (root == null) {
            root = new Arc(focus);
            return;
        }

        Arc parc = root;
        while (parc != null) {
            CircleResultPack rez = intersect(focus, parc);
            if (rez.valid) {
                // New parabola intersects parc.  If necessary, duplicate parc.
             
                if (parc.next != null) {
                    CircleResultPack rezz = intersect(focus, parc.next);
                    if (!rezz.valid){
                        Arc bla = new Arc(parc.focus);
                        bla.prev = parc;
                        bla.next = parc.next;
                        parc.next.prev = bla;
                        parc.next = bla;
                    }
                
                } 
                else {
                    parc.next = new Arc(parc.focus);
                    parc.next.prev = parc;
                }
                parc.next.edge1 = parc.edge1;

                // Add new arc between parc and parc.next.
                Arc bla = new Arc(focus);
                bla.prev = parc;
                bla.next = parc.next;
                parc.next.prev = bla;
                parc.next = bla;

                parc = parc.next; // Now parc points to the new arc.

                // Add new half-edges connected to parc's endpoints.
                parc.edge0 = new Edge(rez.center, parc.prev.focus, parc.focus);
                parc.prev.edge1 = parc.edge0;
                parc.edge1 = new Edge(rez.center, parc.focus, parc.next.focus);
                parc.next.edge0 = parc.edge1;
                
                //save information for removing colinear edges
                rez.center.mech = true;
                rez.center.mech0 = parc.edge0;
                rez.center.mech1 = parc.edge1;
                mechs.add(rez.center);

                // Check for new circle events around the new arc:
                checkCircleEvent(parc, focus.x);
                checkCircleEvent(parc.prev, focus.x);
                checkCircleEvent(parc.next, focus.x);

                return;
            }

            //proceed to next arc
            parc = parc.next;
        }

        // Special case: If p never intersects an arc, append it to the list.        
        parc = root;
        while (parc.next != null)
            parc = parc.next; // Find the last node.
        parc.next = new Arc(focus);
        parc.next.prev = parc;
        Point start = new Point(0, (parc.next.focus.y + parc.focus.y) / 2);
        parc.next.edge0 = new Edge(start, parc.focus, parc.next.focus);
        parc.edge1 = parc.next.edge0;
        
        //focus.verts.addFirst(start);

    }

    void checkCircleEvent(Arc parc, double xpos) {
        // Invalidate any old event.
        if ((parc.event != null) && (parc.event.xpos != xpos))
            parc.event.valid = false;
        parc.event = null;

        if ((parc.prev == null) || (parc.next == null)) return;

        CircleResultPack result = circle(parc.prev.focus, parc.focus, parc.next.focus);
        if (result.valid && result.rightmostX > xpos) {
            // Create new event.
            parc.event = new CircleEvent(result.rightmostX, result.center, parc);
            events.offer(parc.event);
        }

    }

    // Find the rightmost point on the circle through a,b,c.
    CircleResultPack circle(Point a, Point b, Point c) {
        CircleResultPack result = new CircleResultPack();

        // Check that bc is a "right turn" from ab.
        if ((b.x - a.x) * (c.y - a.y) - (c.x - a.x) * (b.y - a.y) > 0) {
            result.valid = false;
            return result;
        }

        // Algorithm from O'Rourke 2ed p. 189.
        double A = b.x - a.x;
        double B = b.y - a.y;
        double C = c.x - a.x;
        double D = c.y - a.y;
        double E = A * (a.x + b.x) + B * (a.y + b.y);
        double F = C * (a.x + c.x) + D * (a.y + c.y);
        double G = 2 * (A * (c.y - b.y) - B * (c.x - b.x));

        if (G == 0) { // Points are co-linear.
            result.valid = false;
            return result;
        }

        // centerpoint of the circle.
        Point o = new Point((D * E - B * F) / G, (A * F - C * E) / G);
        result.center = o;

        // o.x plus radius equals max x coordinate.
        result.rightmostX = o.x + Math.sqrt(Math.pow(a.x - o.x, 2.0) + Math.pow(a.y - o.y, 2.0));

        result.valid = true;
        return result;
    }

    // Will a new parabola at point p intersect with arc i?
    CircleResultPack intersect(Point p, Arc i) {
        CircleResultPack res = new CircleResultPack();
        res.valid = false;
        if (i.focus.x == p.x) return res;

        double a = 0.0;
        double b = 0.0;
        if (i.prev != null) // Get the intersection of i->prev, i.
            a = intersection(i.prev.focus, i.focus, p.x).y;
        if (i.next != null) // Get the intersection of i->next, i.
            b = intersection(i.focus, i.next.focus, p.x).y;

        if ((i.prev == null || a <= p.y) && (i.next == null || p.y <= b)) {
            res.center = new Point(xmin, p.y);

            // Plug it back into the parabola equation to get the x coordinate
            res.center.x = (i.focus.x * i.focus.x + (i.focus.y - res.center.y) * (i.focus.y - res.center.y) - p.x * p.x) / (2 * i.focus.x - 2 * p.x);

            res.valid = true;
            return res;
        }
        return res;
    }

    // Where do two parabolas intersect?
    Point intersection(Point p0, Point p1, double l) {
        Point res = new Point(xmin, ymin);
        Point p = p0;

        if (p0.x == p1.x) {
            res.y = (p0.y + p1.y) / 2;
        } else if (p1.x == l) {
            res.y = p1.y;
        } else if (p0.x == l) {
            res.y = p0.y;
            p = p1;
        } else {
            // Use the quadratic formula.
            double z0 = 2 * (p0.x - l);
            double z1 = 2 * (p1.x - l);

            double a = 1 / z0 - 1 / z1;
            double b = -2 * (p0.y / z0 - p1.y / z1);
            double c = (p0.y * p0.y + p0.x * p0.x - l * l) / z0 - (p1.y * p1.y + p1.x * p1.x - l * l) / z1;

            res.y = (-b - Math.sqrt((b * b - 4 * a * c))) / (2 * a);
        }
        // Plug back into one of the parabola equations.
        res.x = (p.x * p.x + (p.y - res.y) * (p.y - res.y) - l * l) / (2 * p.x - 2 * l);
        return res;
    }

    void finishEdges() {
        // Advance the sweep line so no parabolas can cross the bounding box.
        double l = width * 2 + height;
        
        // Extend each remaining segment to the new parabola intersections.
     
        Arc i = root;
        while (i != null) {         
            if (i.edge1 != null) {
             Point fp = intersection(i.focus, i.next.focus, l * 2);
                i.edge1.finish(fp);
            }
            i = i.next;
        }
       
    }
    
    void polyFinish(){
     //clean up mechs
     //These are single lines formed by 3 colinear points
     //they are a normal result of fortune's algorithm
     //We only want 2 points
        Point unpoint;
        ListIterator<Point> mek = mechs.listIterator(0);
        while (mek.hasNext()) {
         unpoint = mek.next();
         unpoint.mech0.start = unpoint.mech1.end;
         unpoint.mech1.done = false;
        }
     
        
        //create a TreeSet<APoint> for every site
        //APoints will be ordered by thier angle from the site.
        Point pk;
        ListIterator<Point> eeek = pts.listIterator(0);
        while (eeek.hasNext()) {
         pk = eeek.next();
            pk.sortSet = new TreeSet<Point>();
            pk.sverts = new TreeSet<APoint>();
            pk.neighbors = new TreeSet<Point>();
        }
        
        //define the bounding box
        Point[] xing = new Point[4];
        
        //the corners of the bounding box
        topleft = new Point(xmin,ymin);
        topright = new Point(xmax,ymin);
        bottomleft = new Point(xmin,ymax);
        bottomright = new Point(xmax,ymax);
        
        //four sorted sets of Points that lie along each side of the bounding box
        TreeSet[] sides = new TreeSet[4];
        sides[0] = new TreeSet<Point>();//top
        sides[1] = new TreeSet<Point>();//right
        sides[2] = new TreeSet<Point>();//bottom
        sides[3] = new TreeSet<Point>();//left
        
        //add the corners to the lists of sidepoints        
        /*
        sides[0].add(topleft);
        sides[0].add(topright);
        sides[1].add(topright);
        sides[1].add(bottomright);
        sides[2].add(bottomleft);
        sides[2].add(bottomright);
        sides[3].add(topleft);
        sides[3].add(bottomleft);
        */
        
        //the edge in question
        Edge e;
        ListIterator<Edge> j;
        
        /*
         * loop over the edges
         * 
         * trim edges that intersect the bounding box,
         * and add edges between those points of intersection,
         * that close all the polygons that were opened by the trimming.
         * 
         */
        j = output.listIterator(0);
        
        while (j.hasNext()) {
            e = j.next();
            
            if (e.done){             
             //check if the edge intersects with the bounds
             xing[0] = lineIntersect( topleft, topright, e.start, e.end );
             xing[1] = lineIntersect( topright, bottomright, e.start, e.end );
             xing[2] = lineIntersect( bottomleft, bottomright, e.start, e.end );
             xing[3] = lineIntersect( topleft, bottomleft, e.start, e.end );
             
             for (int ii=0; ii<4; ii++){
              //top, right, bottom, left
              //inside function catchs double precision mistakes  
              if (xing[ii] != null && inside(xing[ii]) ){
               // e intersects side ii
               if (ii==0)
                xing[ii].y = ymin;
               else if (ii==1)
                xing[ii].x = xmax;
               else if (ii==2)
                xing[ii].y = ymax;
               else if (ii==3)
                xing[ii].x = xmin;
                
               sides[ii].add(xing[ii]);
               xing[ii].assoc = e;
               
               //shorten e
               if (outside(e.start)){
                e.start = xing[ii];               
               }
               else {
                e.end = xing[ii];                
               }
              }
             }
            }
        }
  
        /*
         * loop over side points
         * all intersections have been found, turn lists of side points into edges with proper
         * site associations
         */
        
        Iterator<Point> jj;
        Point sp,osp; //sidepoint, old sidepoint
        
        for (int ii=0; ii<4; ii++){ // for each side
         if (sides[ii].size() >= 2){ // if there was anything more than just the corner points          
          jj = sides[ii].iterator();
          sp = jj.next();

          while (jj.hasNext()){
           osp = sp;
           sp = jj.next();

           //must determine which site this edge should border
           //will be the site that both edges connected to 
           //sp and osp have in common
           //note this does not handle corner cases
           Point commonSite = null;
           if (osp.assoc != null && sp.assoc != null){
            if (osp.assoc.siteA == sp.assoc.siteA || osp.assoc.siteA == sp.assoc.siteB) {
             commonSite = osp.assoc.siteA;
            } else if (osp.assoc.siteB == sp.assoc.siteA || osp.assoc.siteB == sp.assoc.siteB) {
             commonSite = osp.assoc.siteB;
            }
           }
           
           Edge xo = new Edge(osp, commonSite, null);
           xo.finish(sp);
          }
         }
        }
        
        //associate corners with sites
        Point commonSite = null;
        Point corner = null;
        // standing at the corner, looking in, sp will be the sidepoint on your right and osp will be to your left.
        osp = null;
        sp = null;
        
        for (int ii=0; ii<4; ii++) {
         if (ii == 0){
          corner = topleft;
          sp = (Point)(sides[3].first());
          osp = (Point)(sides[0].first());  
          
         } else if (ii == 1){
          corner = topright;
          sp = (Point)(sides[0].last());
          osp = (Point)(sides[1].first());
         
         } else if (ii == 2){
          corner = bottomright;
          sp = (Point)(sides[1].last());
          osp = (Point)(sides[2].last());         
         } else if (ii == 3){
          corner = bottomleft;
          sp = (Point)(sides[2].first());
          osp = (Point)(sides[3].last());
         }
         
         if (osp != null && sp != null) {
             if ( (osp.assoc.siteA == sp.assoc.siteA || osp.assoc.siteA == sp.assoc.siteB) && osp.assoc.siteA != null ) {
              commonSite = osp.assoc.siteA;
             } else if ( (osp.assoc.siteB == sp.assoc.siteA || osp.assoc.siteB == sp.assoc.siteB) && osp.assoc.siteB != null ) {
              commonSite = osp.assoc.siteB;
             }
             
             Edge xo = new Edge( osp, commonSite, null );
             xo.finish(corner);
             Edge po = new Edge( sp, commonSite, null );             
             po.finish(corner); 
         }
         
        }
  
       
        
        /*
         * loop over edges again
         * add the endpoints of the edge to a sorted set of points,
         * the Points are sorted based on thier angle from the site
         * associated with both of the sites that are next to this edge
         * there are only ever two, but for the boundary cases, one of them will be null,
         * ignore it.
         * 
         */
        j = output.listIterator(0);
        while (j.hasNext()) {
            e = j.next();
            if (e.done){
             //add the edge's endpoints to the sorted set of verts for the sites on each side.
             if (e.siteA != null){
              if (!outside(e.start))
               e.siteA.sortSet.add( e.start );
              if (!outside(e.end))
               e.siteA.sortSet.add( e.end );
              
             }
             if (e.siteB != null){
              if (!outside(e.start))
               e.siteB.sortSet.add( e.start );
              if (!outside(e.end))
               e.siteB.sortSet.add( e.end );
             }
             if ((e.siteA != null) && (e.siteB != null)){
              
              Coordinate[] coords = new Coordinate[2];
              coords[0] = new Coordinate(e.start.x, e.start.y);
              coords[1] = new Coordinate(e.end.x, e.end.y);
              LineString ls = geometryFactory.createLineString(coords);
              
              //boolean isCover = zonePoly.covers(ls);
              boolean isIntersect = zonePoly.intersects(ls);
              //geometryFactory
              
              if (isIntersect /*isCover*/){
                  e.siteA.neighbors.add(e.siteB);
                  e.siteB.neighbors.add(e.siteA);               
              }
              
             }
             
            }
        }
        
        /*
         * loop over the sites
         * for each site, take the sorted set of Points (in acending x value)
         * and add them all to the sorted set of APoints (in acending angle from site)
         */
        Iterator<Point> uu;
        Point eko;
        ListIterator<Point> oo = pts.listIterator(0);
        Point site;
        while (oo.hasNext()) {
            site = oo.next();
            
            if (site.sortSet.size() >= 3){
             uu = site.sortSet.iterator();
             while (uu.hasNext()){
              eko = uu.next();              
              site.sverts.add( new APoint(eko, radFromPoints(site,eko)) );                             
             }
            }
        }
        
        
    }

    boolean outside(Point q){
     //is a Point outside the bounding box?
     return ( (q.x < xmin) || 
        (q.x > xmax) || 
        (q.y < ymin) || 
        (q.y > ymax) );
    }
    
    boolean inside(Point q){
     //is a Point outside the bounding box?
     return ( (q.x >= xmin - 1) && 
        (q.x <= xmax + 1) && 
        (q.y >= ymin - 1) && 
        (q.y <= ymax + 1) );
    }    

    double radFromPoints(Point A, Point B){
        double dx = B.x-A.x;
        double dy = B.y-A.y;
        double rad = Math.atan(dy/dx);
        if(dx<0) rad+=Math.PI;
        return rad;
    }

    class Point implements Comparable<Point> {

        public double x, y;
        public int key;
        
        public TreeSet<Point> sortSet;
        public TreeSet<APoint> sverts;
        public TreeSet<Point> neighbors;
        public Edge assoc;
        
        public boolean mech = false;
        public Edge mech0, mech1;
        
        public int fillcolor;

        public Point(double X, double Y) {
            x = X;
            y = Y;
        }

        public int compareTo(Point foo) {
         //return zero only if this is the same object.
         if (this == foo) {
          return 0;
         } else {
          int rx = ((Double) this.x).compareTo((Double) foo.x);
          if (rx != 0){
           return rx;
          } else {
           int ry = ((Double) this.y).compareTo((Double) foo.y);
           if (ry != 0){
            return ry;
           } else {
            return 1;
           }
          }
         }
        }
        
    }
    
    class APoint implements Comparable<APoint>{
     
     public double x, y;
     public double angle;
     
     public APoint(Point p, double ang){
      x = p.x;
      y = p.y;
      angle = ang;
     }
     
        public int compareTo(APoint foo) {
         //return zero only if this is the same object.
         if (this == foo) {
          return 0;
         } else {
          return ((Double) this.angle).compareTo((Double) foo.angle);
         }
        }
    }

    class CircleEvent implements Comparable<CircleEvent> {

        public double xpos;
        public Point p;
        public Arc a;
        public boolean valid;

        public CircleEvent(double X, Point P, Arc A) {
            xpos = X;
            a = A;
            p = P;
            valid = true;
        }

        public int compareTo(CircleEvent foo) {
            return ((Double) this.xpos).compareTo((Double) foo.xpos);
        }
    }

    class Edge {

        public Point start, end;
        public boolean done;
        public Point siteA, siteB;

        public Edge(Point p, Point sitea, Point siteb) {
            start = p;
            end = new Point(0, 0);
            done = false;
            siteA = sitea;
            siteB = siteb;
            output.add(this);
        }

        public void finish(Point p) {
            if (done) {
                return;
            }
            end = p;
            done = true;
        }
    }

    class Arc {
        //parabolic arc is the set of points eqadistant from a focus point and the beach line

        public Point focus;
        //these object exsit in a linked list
        public Arc next, prev;
        //
        public CircleEvent event;
        //
        public Edge edge0, edge1;

        public Arc(Point p) {
            focus = p;
            next = null;
            prev = null;
            event = null;
            edge0 = null;
            edge1 = null;
        }
    }
    
    class CircleResultPack {
        public boolean valid;
        public Point center;
        public double rightmostX;
    }
    
    Point midpoint(Point a, Point b){
     return new Point( (a.x+b.x)/2, (a.y+b.y)/2 );
    }
    
    Point lineIntersect(Point A, Point B, Point C, Point D){
     /*
      * if line segment AB intersects line segment CD,
      * return the point of intersection
      * else return null
      */
     double ex = B.x - A.x;
     double ey = B.y - A.y;
     double fx = D.x - C.x;
     double fy = D.y - C.y;
     double h = ( (A.x-C.x) * (-ey) + (A.y-C.y) * ex) / (fx * (-ey) + fy * ex);
     if (0 <= h && h <= 1){
      return new Point( C.x + h * fx,
            C.y + h * fy );
     } else return null;
    }     
}

10) После запуска java-программы проверим наличие данных в таблице D_VORONOI_POLY с помощью sqldeveloper (рекомендую скачать его и использовать для работы со Spatial-объектами БД Oracle)


11) Дальше вы сами вправе решать, что делать с получившимися полигонами... склеить их, или использовать в своих эвристических алгоритмах...
Но следует помнить о ряде ограничений данного решения:
- Диаграмма строится по точкам внутри замкнутого полигона, если полигон содержит анклавы - ситуацию придется как-то обрабатывать;
- Вам желательно самим предусмотреть обработку точек-дубликатов (когда несколько РАЗНЫХ домов имеют одинаковые координаты);
- Если ограничивающий полигон не является выпуклым многоугольником, то возможны "некрасивые ситуации" (следует помнить, что сама диаграмма строится внутри MBR - "минимального охватывающего точки прямоугольник" - и уже затем от каждой ячейки Вороного "отрезаются кусочки" за границами охватывающего полигона).


На этом все, надеюсь кому-то это поможет!

Комментариев нет:

Отправить комментарий