F# and Path Reduction

F# and Path Reduction
Kit Eason - @kitlovesfsharp – www.kiteason.com – [email protected]
Norway – way too complicated!
Way, way too complicated!
Ramer–Douglas–Peucker
A record type
type Point = {Long: double; Lat : double}
Distance from line
let private findPerpendicularDistance p p1 p2 =
if (p1.Long = p2.Long) then
abs(p.Long - p1.Long)
else
let slope = (p2.Lat - p1.Lat) / (p2.Long - p1.Long)
let intercept = p1.Lat - (slope * p1.Long)
abs(slope * p.Long - p.Lat + intercept) / sqrt((pown slope 2) + 1.)
Recursive algorithm
let rec Reduce epsilon (points : Point[]) =
if points.Length < 3 || epsilon = 0. then
points
else
let firstPoint = points.[0]
let lastPoint = points.[points.Length - 1]
let mutable index = -1
let mutable dist = 0.0
for i in 1..points.Length-1 do
let cDist = findPerpendicularDistance points.[i] firstPoint lastPoint
if (cDist > dist) then
dist <- cDist
index <- i
if (dist > epsilon) then
let l1 = points.[0..index]
let l2 = points.[index..]
let r1 = Reduce epsilon l1
let r2 = Reduce epsilon l2
Array.append (r1.[0..r1.Length-2]) r2
else
[|firstPoint; lastPoint|]
let rec Reduce epsilon (points : Point[]) =
if points.Length < 3 || epsilon = 0. then
points
else
let firstPoint = points.[0]
let lastPoint = points.[points.Length - 1]
let mutable index = -1
let mutable dist = 0.0
for i in 1..points.Length-1 do
let cDist = findPerpendicularDistance points.[i] firstPoint lastPoint
if (cDist > dist) then
dist <- cDist
index <- i
if (dist > epsilon) then
let l1 = points.[0..index]
let l2 = points.[index..]
let r1 = Reduce epsilon l1
let r2 = Reduce epsilon l2
Array.append (r1.[0..r1.Length-2]) r2
else
[|firstPoint; lastPoint|]
The data
nan nan
8.299972 54.778750
8.300778 54.778278
8.300806 54.775778
8.302417 54.775000
8.302444 54.772472
8.300778 54.771611
8.300750 54.767444
nan nan
8.277417 54.764111
8.277417 54.775750
8.279083 54.776611
8.279111 54.797444
Reading the data
let private ReadData fileName =
fileName
|> File.ReadAllLines
|> Array.breakOn (fun line -> line = "nan nan")
|> Array.Parallel.map ToPoints
|> FilterCount 100
Breaking into arrays
let breakOn (f : 'a -> bool) (a : array<'a>) =
[|
let result = ResizeArray()
for x in a do
if f x then
yield result |> Array.ofSeq
result.Clear()
else
result.Add(x)
yield result |> Array.ofSeq
|]
Converting to points
let private ToPoints (lines : array<string>) =
lines
|> Array.Parallel.map (fun line -> line.Split [|'\t'|])
|> Array.Parallel.map (fun items -> items |> Array.map Double.Parse)
|> Array.choose (fun doubles -> match doubles with
| [|long; lat|] -> Some {Long=long; Lat=lat}
| _ -> None)
Eliminating tiny islands (and lakes!)
let private FilterCount minCount groups =
groups
|> Array.filter (fun g -> Array.length g > minCount)
Calling the RDP algorithm
let Simplify e polyLines =
polyLines
|> Array.Parallel.map (fun p ->
p |> Reduce.Reduce e)
ε=0
ε = 0.001
ε = 0.01
ε = 0.1
ε=1
Out-takes
Resources
 www.github.com/misterspeedy/coastline
 www.fsharp.org
 www.fsharpforfunandprofit.com
 Chris Smith’s “Animal Guide”