This project contains utilities for
- creating a bounding box around a given GPS location of a given size (returns the GPS locations of the corners of the box)
- calculating the population within areas of a given size and center point
See below, or the test code, for how to use these utilities.
libraryDependencies += "com.github.shafiquejamal" %% "gis-util" % "0.0.16"
This library contains code for creating the following types of Area
s:
- Bounding box (square)
- Circle
- Ellipse
Areas are able to determine whether a given point of interest (PointOfInterest
) lies within them. A point of interest is a point specified by an id and a GPSCoordinate
. They can also have a population as a state variable, which is a collection of PointOfInterest
that they contain.
Given a center point and edge length, you can create a square bounding box that is axis aligned, and that can tell you whether a given point is inside it. You can create the bounding box using:
- four GPS coordinates that are corners of the box, in the following order: SW, NW, NE, SE, as well as the center point and optionally the edge length (just be sure to provide an accurate edge length. ). If you do not specify the edge length, then it is calculated from the SW and NW GPS coordinates provided. The following code creates a square bounding box with an edge length of 250m. Note that the edge length should be specified in km.
BoundingBox(
GPSCoordinates(Lat(39.112927099380826),Lng(-94.62890723880348)),
GPSCoordinates(Lat(39.11517290061917),Lng(-94.62890723880348)),
GPSCoordinates(Lat(39.11517290061917),Lng(-94.62601276119655)),
GPSCoordinates(Lat(39.112927099380826),Lng(-94.62601276119655)), 0.250
)
BoundingBox(
GPSCoordinates(Lat(39.112927099380826),Lng(-94.62890723880348)),
GPSCoordinates(Lat(39.11517290061917),Lng(-94.62890723880348)),
GPSCoordinates(Lat(39.11517290061917),Lng(-94.62601276119655)),
GPSCoordinates(Lat(39.112927099380826),Lng(-94.62601276119655))
)
- a center point and edge length
val centerPoint = GPSCoordinates(Lat(39.11405), Lng(-94.62746))
val bb250m = BoundingBox.from(centerPoint, 0.250)
To determine whether a given point lies within the bounding box, use the bounding box's .contain
method:
val candidatePointInside250 = GPSCoordinates(Lat(39.11516), Lng(-94.62890))
val candidatePointOutside250 = GPSCoordinates(Lat(39.11518), Lng(-94.62890))
bb250m boundaryWraps candidatePointInside250 // true
bb250m boundaryWraps candidatePointOutside250 // false
To determine the number of points of interest (e.g. number of people, stores, etc.) within the bounding box, you can send it all of the points of interest, and it will retain in its state only those that lie within its boundaries - you then call the nWithin
method:
case class Person(override val id: String, override val location: GPSCoordinate) extends PointOfInterest[String]
val persons = Seq(
Person("1_250_0_0", GPSCoordinate(Lat(39.11518), Lng(-94.62890))),
Person("2_250_1_0", GPSCoordinate(Lat(39.11500), Lng(-94.62890))),
Person("3_250_1_0", GPSCoordinate(Lat(39.11490), Lng(-94.62870))),
Person("4_250_1_1", GPSCoordinate(Lat(39.11354), Lng(-94.62680))),
Person("5_250_1_1", GPSCoordinate(Lat(39.11294), Lng(-94.62605))),
Person("6_250_0_1", GPSCoordinate(Lat(39.11295), Lng(-94.62600))),
Person("7_250_0_0", GPSCoordinate(Lat(39.11136), Lng(-94.62380))))
val centerPoint = GPSCoordinate(Lat(39.11405), Lng(-94.62746))
val bb250m = BoundingBox.from(centerPoint, 0.250)
val bb500m = BoundingBox.from(centerPoint, 0.500)
(bBox250m `with` persons).nWithin // 4
(bBox500m `with` persons).nWithin // 6
The following image illustrates the concept:
The upper left square, KC1, has an edge length of 250m and contains 4 markers. The lower left square, KC2, also has an edge length of 250m and contains 3 markers. When the edge lengths are increased to 500m, the areas contain 6 and 4 markers respectively. The code above is for the box that is the upper left square.
Circle extends the same Area trait as does square, and so has the nWithin
, boundaryWraps
and other methods.
A Circle is created by specifying a location and a radius:
val circle = BoundingCircle("c1", GPSCoordinate(Lat(39.11405), Lng(-94.62746)), 2)
val pointOutside = GPSCoordinate(Lat(39.120261171336146), Lng(-94.64946337892016))
val pointInside = GPSCoordinate(Lat(39.120138217988696), Lng(-94.64902763079968))
circle boundaryWraps pointOutside // false
circle boundaryWraps pointInside // true
An Ellipse is created by specifying a location, semi-major axis, semi-minor axis, and tilt from West.:
val ellipse = BoundingEllipse("e1", GPSCoordinate(Lat(39.11405), Lng(-94.62746)), 2, 1, 20)
ellipse boundaryWraps pointOutside // false
ellipse boundaryWraps pointInside // true
The picture below illustrates the concept. The circle and the ellipse are able to determine whether each point (marked by the green markers at the upper left) are inside or outside of its boundary.
Bounding box KC1 above of 250m edge length can be divided into 9 non-overlapping, identically-sized bounding boxes that completely cover the area of the original bounding box.
All that is needed is the center point of the original bounding box, the edge length of the original bounding box, and the number of slices into which to cut each edge. From this, new center points and the edge length of each (the latter being identical for all) can then be calculated.
The following image shows the new center points that the code below calculates.
val centerPoint = GPSCoordinates(Lat(39.11405), Lng(-94.62746))
CatchmentAreasCreator.from(centerPoint, 0.250, 3)
This should yield the following points:
GPSCoordinates(Lat(39.11330139958722),Lng(-94.62842482586905)),
GPSCoordinates(Lat(39.11330139958722),Lng(-94.62746000000003)),
GPSCoordinates(Lat(39.11330139958722),Lng(-94.62649517413101)),
GPSCoordinates(Lat(39.11405),Lng(-94.62842482586905)),
GPSCoordinates(Lat(39.11405),Lng(-94.62746000000003)),
GPSCoordinates(Lat(39.11405),Lng(-94.62649517413101)),
GPSCoordinates(Lat(39.11479860041278),Lng(-94.62842482586905)),
GPSCoordinates(Lat(39.11479860041278),Lng(-94.62746000000003)),
GPSCoordinates(Lat(39.11479860041278),Lng(-94.62649517413101)))
https://www.movable-type.co.uk/scripts/latlong.html
https://stackoverflow.com/questions/18295825/determine-if-point-is-within-bounding-box