Public
Edited
Oct 27, 2023
7 forks
14 stars
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
area = r=>r.getJTSGeom().getArea() / 1e6
Insert cell
Insert cell
convexity = r=> {
return r.getJTSGeom().getArea() / r.getJTSGeom().convexHull().getArea()
}
Insert cell
Insert cell
circularity = r=> {

//compute radius of the circle with same area
const area = r.getJTSGeom().getArea()
const radius = Math.sqrt(area / Math.PI)
//build circle geometry, as buffer of the center
const cGeom = r.getJTSGeom().getCentroid().buffer(radius)

//compute intersection
let inter
try {
inter = cGeom.intersection(r.getJTSGeom())
} catch (error) {
//in case computation fails, run it on buffer(0)
inter = cGeom.buffer(0).intersection(r.getJTSGeom().buffer(0))
}

//return ratio
//console.log(r.properties.NUTS_NAME, inter.getArea()/area)
return inter.getArea() / area
}
Insert cell
Insert cell
circularity2 = r=> {
//TODO remove holes ? pre-simplify with closure ?
const radius = new jsts.algorithm.MinimumBoundingCircle(r.getJTSGeom()).getRadius();
const ca = Math.PI *radius *radius;
return r.getJTSGeom().getArea() / ca
}
Insert cell
Insert cell
circularity3 = r=> {
//it requires a simplified geometry - because the perimeter depends very much on the geometrical granularity.
const g = r.getJTSGeomSimplified()
//TODO remove holes ?
return 2*Math.sqrt(g.getArea()) / g.getLength()
}
Insert cell
Insert cell
elongation = r=> {
//get minimum bounding rectangle coordinates
const mbr = new jsts.algorithm.MinimumDiameter(r.getJTSGeom()).getMinimumRectangle().getCoordinates()
//compute length of two consecutive sides
const s1 = Math.hypot(mbr[0].x-mbr[1].x, mbr[0].y-mbr[1].y)
const s2 = Math.hypot(mbr[1].x-mbr[2].x, mbr[1].y-mbr[2].y)
const e = s1>s2? s2/s1 : s1/s2
return 1-e
}
Insert cell
Insert cell
orientationElongationThreshold = 0.333
Insert cell
orientation = r => {

//get minimum bounding rectangle coordinates
const mbr = new jsts.algorithm.MinimumDiameter(r.getJTSGeom()).getMinimumRectangle().getCoordinates()

//compute length of two consecutive sides
const s1 = Math.hypot(mbr[0].x-mbr[1].x, mbr[0].y-mbr[1].y)
const s2 = Math.hypot(mbr[1].x-mbr[2].x, mbr[1].y-mbr[2].y)

//compute elongation
let e = s1>s2? s2/s1 : s1/s2
e = 1-e

//if elongation not sufficient, then measure not defined
if(e < orientationElongationThreshold) return undefined;

//compute angle of the length, in radian
let a = s1>s2?
Math.atan2(mbr[0].y-mbr[1].y, mbr[0].x-mbr[1].x) :
Math.atan2(mbr[1].y-mbr[2].y, mbr[1].x-mbr[2].x)

//in degree, within [0,180]
a = a * 180 / Math.PI
if(a<0) a+=180

return a
}

Insert cell
Insert cell
rectangleness= r => {

//compute minimum bounding rectangle
const mbr = new jsts.algorithm.MinimumDiameter(r.getJTSGeom()).getMinimumRectangle()
//compute areas
const area = r.getJTSGeom().getArea()
const mbrArea = mbr.getArea()
//compute center
const c = r.getJTSGeom().getCentroid().getCoordinate()
//compute scale ratio
const scaleRatio = Math.sqrt(area/mbrArea)
//build scale transform
const at = new jsts.geom.util.AffineTransformation()
at.translate(-c.x, -c.y)
at.scale(scaleRatio,scaleRatio)
at.translate(c.x, c.y)
//scale MBR
const scaledMBR = at.transform(mbr)
//check scaled mbr and region have same are
//console.log(scaledMBR.getArea()- area)
//compute intersection
let inter
try {
inter = scaledMBR.intersection(r.getJTSGeom())
} catch (error) {
//in case computation fails, run it on buffer(0)
inter = scaledMBR.buffer(0).intersection(r.getJTSGeom().buffer(0))
}

//return ratio
//console.log(r.properties.NUTS_NAME, inter.getArea() / area)
return inter.getArea()/area
}

Insert cell
Insert cell
rectangleness2 = r => {
//get minimum bounding rectangle
const mbr = new jsts.algorithm.MinimumDiameter(r.getJTSGeom()).getMinimumRectangle()
return r.getJTSGeom().getArea() / mbr.getArea()
}
Insert cell
Insert cell
squareness = r => {
const rect = rectangleness(r)
const elong = elongation(r)
return rect * (1-elong)
}
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
regionsJSTS = regionsFiltered.map(r=>{

//create jts version of the geometry
r.getJTSGeom = () => {
if(!r.jtsGeom__) r.jtsGeom__ = GJreader.read(r.geometry)
return r.jtsGeom__
}

//create generalised version of the geometry
r.getJTSGeomSimplified = () => {
if(!r.jtsGeomSimplified__) {
r.jtsGeomSimplified__ = r.getJTSGeom()

//simplification parameter: the geometry diagonal divided by nb of pixels
const d = r.getJTSGeom().getEnvelopeInternal().getDiameter();
const res = 0.7 * d/regionWidth

//simple generalisation process: ramer-douglass-peucker filter, with erosion/closure
r.jtsGeomSimplified__ = jsts.simplify.DouglasPeuckerSimplifier.simplify(r.jtsGeomSimplified__, 0.5*res)
r.jtsGeomSimplified__ = r.jtsGeomSimplified__.buffer(2*res)
r.jtsGeomSimplified__ = jsts.simplify.DouglasPeuckerSimplifier.simplify(r.jtsGeomSimplified__, 1*res)
r.jtsGeomSimplified__ = r.jtsGeomSimplified__.buffer(-2*res)
r.jtsGeomSimplified__ = jsts.simplify.DouglasPeuckerSimplifier.simplify(r.jtsGeomSimplified__, 1*res)
}
return r.jtsGeomSimplified__
}

return r;
})
Insert cell
Insert cell
regionsMeasured = regionsJSTS.map(r=>{
try {
r.measure = measureFun(r)
} catch (error) {
console.log("Failed computing measure for ",r.properties.name,r.properties.NAME_LATN)
r.measure = undefined
}
return r
})
Insert cell
Insert cell
regionsFilteredSorted =
regionsMeasured
.filter( r=> r.measure != undefined )
.sort(function (r1, r2) {
return r1.measure - r2.measure;
})
Insert cell

Purpose-built for displays of data

Observable is your go-to platform for exploring data and creating expressive data visualizations. Use reactive JavaScript notebooks for prototyping and a collaborative canvas for visual data exploration and dashboard creation.
Learn more