Rev 2049 |
Blame |
Compare with Previous |
Last modification |
View Log
| RSS feed
/**
* Copyright (C) 2006 Steve Ratcliffe
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License 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.
*
* Author: steve
* Date: 23-Dec-2006
*/
package uk.me.parabola.mkgmap.general;
import java.util.ArrayList;
import java.util.List;
import uk.me.parabola.imgfmt.app.Coord;
/**
* A shape or polygon is just the same as a line really as far as I can tell.
* There are some things that you cannot do with them semantically.
*
* @author Steve Ratcliffe.
*/
public class MapShape
extends MapLine
{// So top code can link objects from here
public MapShape
() {
}
MapShape
(MapShape s
) {
super(s
);
}
public MapShape copy
() {
return new MapShape
(this);
}
public void setDirection
(boolean direction
) {
throw new IllegalArgumentException(
"can't set a direction on a polygon");
}
/**
* Checks if a point is contained within this shape. Points on the
* edge of the shape are considered inside.
*
* @param co point to check
* @return true if point is in shape, false otherwise
*/
public boolean contains
(Coord co
) {
return contains
(this.
getPoints(), co,
true);
}
/**
* Checks if a point is contained within a shape.
*
* @param points points that define the shape
* @param target point to check
* @param onLineIsInside if a point on the line should be considered inside the shape
* @return true if point is contained within the shape, false if the target point is outside the shape
*/
private static boolean contains
(List<Coord
> points, Coord target,
boolean onLineIsInside
) {
// implementation of the Ray casting algorithm as described here:
// http://en.wikipedia.org/wiki/Point_in_polygon
// with inspiration from:
// http://www.visibone.com/inpoly/
if (points.
size() < 3)
return false;
// complete the shape if we're dealing with a MapShape that is not closed
Coord start = points.
get(0);
Coord end = points.
get(points.
size() -
1);
if (!start.
equals(end
)) {
// make copy of the shape's geometry
List<Coord
> pointsTemp =
new ArrayList<Coord
>(points.
size() +
1);
for (Coord coord : points
) {
pointsTemp.
add(new Coord
(coord.
getLatitude(), coord.
getLongitude()));
}
pointsTemp.
add(new Coord
(start.
getLatitude(), start.
getLongitude()));
points = pointsTemp
;
}
int xtarget = target.
getLatitude();
int ytarget = target.
getLongitude();
boolean inside =
false;for
(int i =
0; i
< points.
size() -
1; i++
) {
// apply transformation points to change target point to (0,0)
int x0 = points.
get(i
).
getLatitude() - xtarget
;
int y0 = points.
get(i
).
getLongitude() - ytarget
;
int x1 = points.
get(i+
1).
getLatitude() - xtarget
;
int y1 = points.
get(i+
1).
getLongitude() - ytarget
;
// ensure that x0 is smaller than x1 so that we can just check to see if the line intersects the y axis easily
if (x0
> x1
) {
int xtemp = x0
;
int ytemp = y0
;
x0 = x1
;
y0 = y1
;
x1 = xtemp
;
y1 = ytemp
;
}
// use (0,0) as target because points already transformed
if (isPointOnLine
(x0, y0, x1, y1,
0,
0))
return onLineIsInside
;
// explanation of if statement
//
// (x0 < 0 && x1 >= 0):
// are the x values between the y axis? only include points from the right
// with this check so that corners aren't counted twice
//
// (y0 * (x1 - x0) > (y1 - y0) * x0):
// from y = mx + b:
// => b = y0 ((y1 - y0) / (x1 - x0)) * x0
// for intersection, b > 0
// from y = mx + b, b = y - mx
// => y - mx > 0
// => y0 - ((y1 - y0) / (x1 - x0)) * x0 > 0
// => y0 > ((y1 - y0) / (x1 - x0)) * x0
// from 'if (x0 > x1)', x1 >= x0
// => x1 - x0 >=0
// => y0 * (x1 - x0) > (y1 - y0) * x0
if ((x0
< 0 && x1
>=
0) && (y0
* (x1 - x0
)) > ((y1 - y0
) * x0
))
inside =
!inside
;
}
return inside
;
}
/**
* Checks if a point is on a line.
*
* @param x0 x value of first point in line
* @param y0 y value of first point in line
* @param x1 x value of second point in line
* @param y1 y value of second point in line
* @param xt x value of target point
* @param yt y value of target point
* @return return true if point is on the line, false if the point isn't on the line
*/
private static boolean isPointOnLine
(int x0,
int y0,
int x1,
int y1,
int xt,
int yt
) {
// this implementation avoids using doubles
// apply transformation points to change target point to (0,0)
x0 -= xt
;
y0 -= yt
;
x1 -= xt
;
y1 -= yt
;
// ensure that x0 is smaller than x1 so that we can just check to see if the line intersects the y axis easily
if (x0
> x1
) {
int xtemp = x0
;
int ytemp = y0
;
x0 = x1
;
y0 = y1
;
x1 = xtemp
;
y1 = ytemp
;
}
// if a point is on the edge of shape (on a line), it's considered outside the shape
// special case if line is on y-axis
if (x0 ==
0 && x1 ==
0) {
// ensure that y0 is smaller than y1 so that we can just check if the line intersects the x axis
if (y0
> y1
) {
int ytemp = y0
;
y0 = y1
;
y1 = ytemp
;
}
// test to see if we have a vertical line touches x-axis
if (y0
<=
0 && y1
>=
0)
return true;
// checks if point is on the line, see comments in contain() for derivation of similar
// formula - left as an exercise to the reader ;)
} else if ((x0
<=
0 && x1
>=
0) && (y0
* (x1 - x0
)) ==
((y1 - y0
) * x0
)) {
return true;
}
return false;
}
}