Haskell – Convex Hull – Graham Scan
===================================
December 16, 2017
Playing with Convex Hulls (via Graham Scan) and SVG Export in Haskell:
This is an embedded SVG generated by the Haskell programm below:
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ {.haskell .numberLines}
---------------------------------------------------------------------------------------------
--
-- GRAHAM SCAN IMPLEMENTATION
--
-- This little haskell programm calulates the Convex Hull for a set of 2D points.
-- It ships wit a main function that feeds the Graham Scan algorithm with some
-- random points and generates a simple SVG of the input points and resulting envelope.
-- A simple SVG encoder is included.
--
-- Alogrithm used: https://en.wikipedia.org/wiki/Graham_scan
--
-- CREDITS --
--
-- Michal Idziorek
-- 16 December 2017
--
---------------------------------------------------------------------------------------------
import Data.List
import System.Random
---------------------------------------------------------------------------------------------
-- GRAHAM SCAN --
-- Three points are clockwise if ccw < 0.
ccw (p1x,p1y) (p2x,p2y) (p3x,p3y) = (p2x - p1x)*(p3y - p1y) - (p2y - p1y)*(p3x - p1x)
-- Calculate the slope defined by 2 points. (return Infinity, if points are identical).
slope (ax,ay) (bx,by) | (ax,ay ) == (bx,by) = 1/0 -- Infinity
| otherwise = (bx-ax)/(by-ay)
-- Comparison function to sort points counterclockwise (given a reference point).
slope_cmp a b c = compare (slope a c) (slope a b)
-- Comparison function using the y and x coordinates for ordering.
graham_cmp (ax,ay) (bx,by) | ay /= by = compare ay by
| otherwise = compare ax bx
-- Graham scan on prepared data. this will calculate the convex hull.
graham_calc [] hs = hs
graham_calc (x1:xs) hh | length(hh) < 2 = graham_calc xs (x1:hh)
graham_calc xx@(x1:xs) hh@(h1:h2:hs) | ccw x1 h1 h2 < 0 = graham_calc xs (x1:hh)
| otherwise = graham_calc xx (h2:hs)
-- Find the starting point, sort all points counterclockwise and perform the graham scan.
graham xs = graham_calc sortedPoints []
where minPoint = minimumBy graham_cmp xs
sortedPoints = sortBy (slope_cmp minPoint) xs
---------------------------------------------------------------------------------------------
-- XML ENCODING--
xml_attr (x:xs) = x++"=\""++(head xs)++"\" "
xml_enc tag attrs body = "<"++tag++" "++xml_attrs++">"++body++""++tag++">"
where xml_attrs = unlines $ map xml_attr attrs
-- SVG ENCODING --
-- hardcoded scaling and panning function
svg_transf x = x*30+5
line_to_svg ((x1,y1),(x2,y2)) = xml_enc "line" [["x1",show lx1],["y1",show ly1],
["x2",show lx2],["y2",show ly2],
["style", "stroke:rgb(255,0,0);stroke-width:2"]] ""
where lx1=svg_transf x1
lx2=svg_transf x2
ly1=svg_transf y1
ly2=svg_transf y2
point_to_svg (x,y) = xml_enc "circle" [["cx",show cx],["cy",show cy],["r","5"],
["fill","rgb(30,150,"++(show (floor dist))++")"]] ""
where cx=svg_transf x
cy=svg_transf y
dist= (sqrt ((x-5)*(x-5) + (y-5)*(y-5)))*255/8
-- draws SVG points and lines (in hardcoded sizes and colors)
svg_draw p l = xml_enc "svg" [style,["width","330"],["height","330"]] body
where style = ["style",
"background-color:black;border:3px solid green;margin:2px;"]
body = (unlines (map point_to_svg p )) ++
(unlines (map line_to_svg l ))
-- calculate convex hull and generate svg
svg_graham xs = svg_draw xs (zip hull hull_open)
where hull_open = graham xs
hull = (last hull_open) : hull_open
-- RANDOMIZING --
randomPoints g cnt = take cnt (zip r10a r10b)
where r5 = randomRs (0,5) g :: [Double]
r10a =zipWith (+) r5 (drop cnt r5)
r10b =zipWith (+) (drop (2*cnt) r5) (drop (3*cnt) r5)
---------------------------------------------------------------------------------------------
-- MAIN --
-- Note that this is the only place of impurity in this source-file.
-- Is is subject to side effects due to I/O (we are writng to stdout)
-- and the random number generator.
main = do
g <- newStdGen
putStr (svg_graham (randomPoints g 25))
putStr (svg_graham (randomPoints g 50))
putStr (svg_graham (randomPoints g 100))
putStr (svg_graham (randomPoints g 250))
putStr (svg_graham (randomPoints g 500))
putStr (svg_graham (randomPoints g 1500))
---------------------------------------------------------------------------------------------
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~