Сегодня хочу описать решение по генерации диаграммы Вороного с помощью 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 - "минимального охватывающего точки прямоугольник" - и уже затем от каждой ячейки Вороного "отрезаются кусочки" за границами охватывающего полигона).

На этом все, надеюсь кому-то это поможет!
Комментариев нет:
Отправить комментарий