add Ox.containsArea (true if an area contains another one), Ox.intersectAreas (the area/s contained in all areas) and Ox.joinAreas (the smallest area contaning all areas)
This commit is contained in:
parent
461233e87b
commit
9a61fdbbab
1 changed files with 224 additions and 0 deletions
|
@ -17,6 +17,13 @@
|
|||
});
|
||||
}
|
||||
|
||||
function splitArea(area) {
|
||||
return Ox.crossesDateline(area.sw, area.ne) ? [
|
||||
{sw: area.sw, ne: {lat: area.ne.lat, lng: 180}},
|
||||
{sw: {lat: area.sw.lat, lng: -180}, ne: area.ne}
|
||||
] : [area];
|
||||
}
|
||||
|
||||
/*@
|
||||
Ox.crossesDateline <f> Returns true if a given line crosses the dateline
|
||||
> Ox.crossesDateline({lat: 0, lng: -90}, {lat: 0, lng: 90})
|
||||
|
@ -24,6 +31,7 @@
|
|||
> Ox.crossesDateline({lat: 0, lng: 90}, {lat: 0, lng: -90})
|
||||
true
|
||||
@*/
|
||||
// FIXME: argument should be {w: ..., e: ...}
|
||||
Ox.crossesDateline = function(pointA, pointB) {
|
||||
return pointA.lng > pointB.lng;
|
||||
};
|
||||
|
@ -31,6 +39,7 @@
|
|||
/*@
|
||||
Ox.getArea <f> Returns the area in square meters of a given rectancle
|
||||
@*/
|
||||
// FIXME: argument should be {sw: ..., ne: ...}
|
||||
Ox.getArea = function(pointA, pointB) {
|
||||
/*
|
||||
area of a ring between two latitudes:
|
||||
|
@ -222,6 +231,221 @@
|
|||
return point.lat < Ox.MIN_LATITUDE || point.lat > Ox.MAX_LATITUDE;
|
||||
};
|
||||
|
||||
/*@
|
||||
Ox.containsArea <f> Returns true if an area contains another area
|
||||
<script>
|
||||
Ox.test.areas = [
|
||||
{sw: {lat: -30, lng: -30}, ne: {lat: 30, lng: 30}},
|
||||
{sw: {lat: -20, lng: -40}, ne: {lat: 20, lng: 40}},
|
||||
{sw: {lat: -30, lng: 150}, ne: {lat: 30, lng: -150}},
|
||||
{sw: {lat: 10, lng: -170}, ne: {lat: 20, lng: -160}}
|
||||
];
|
||||
</script>
|
||||
> Ox.containsArea(Ox.test.areas[0], Ox.test.areas[1])
|
||||
false
|
||||
> Ox.containsArea(Ox.test.areas[2], Ox.test.areas[3])
|
||||
true
|
||||
@*/
|
||||
Ox.containsArea = function(areaA, areaB) {
|
||||
// If an area crosses the dateline,
|
||||
// we split it into two parts,
|
||||
// west and east of the dateline
|
||||
var areas = [areaA, areaB].map(splitArea),
|
||||
ret;
|
||||
function contains(areaA, areaB) {
|
||||
return areaA.sw.lat <= areaB.sw.lat
|
||||
&& areaA.sw.lng <= areaB.sw.lng
|
||||
&& areaA.ne.lat >= areaB.ne.lat
|
||||
&& areaA.ne.lng >= areaB.ne.lng;
|
||||
}
|
||||
// For each part of the inner area, test if it
|
||||
// is contained in any part of the outer area
|
||||
Ox.forEach(areas[1], function(area1) {
|
||||
Ox.forEach(areas[0], function(area0) {
|
||||
ret = contains(area0, area1);
|
||||
// Break if the outer part contains the inner part
|
||||
return !ret;
|
||||
});
|
||||
// Break if no outer part contains the inner part
|
||||
return ret;
|
||||
});
|
||||
return ret;
|
||||
};
|
||||
|
||||
/*@
|
||||
Ox.intersectAreas <f> Returns the intersection of two areas, or null
|
||||
<script>
|
||||
Ox.test.areas = [
|
||||
{sw: {lat: -10, lng: -10}, ne: {lat: 0, lng: 0}},
|
||||
{sw: {lat: 0, lng: 0}, ne: {lat: 10, lng: 10}},
|
||||
{sw: {lat: -30, lng: 150}, ne: {lat: 30, lng: -150}},
|
||||
{sw: {lat: 25, lng: -155}, ne: {lat: 35, lng: -145}}
|
||||
];
|
||||
</script>
|
||||
> Ox.intersectAreas([Ox.test.areas[0], Ox.test.areas[1]])
|
||||
{sw: {lat: 0, lng: 0}, ne: {lat: 0, lng: 0}}
|
||||
> Ox.intersectAreas([Ox.test.areas[2], Ox.test.areas[3]])
|
||||
{sw: {lat: 25, lng: -155}, ne: {lat: 30, lng: -150}}
|
||||
@*/
|
||||
Ox.intersectAreas = function(areas) {
|
||||
// FIXME: handle the a corner case where
|
||||
// two areas have two intersections
|
||||
var intersections, ret;
|
||||
// If an area crosses the dateline,
|
||||
// we split it into two parts,
|
||||
// west and east of the dateline
|
||||
areas = areas.map(splitArea);
|
||||
ret = areas[0];
|
||||
function intersect(areaA, areaB) {
|
||||
return areaA.sw.lat > areaB.ne.lat
|
||||
|| areaA.sw.lng > areaB.ne.lng
|
||||
|| areaA.ne.lat < areaB.sw.lat
|
||||
|| areaA.ne.lng < areaB.sw.lng
|
||||
? null : {
|
||||
sw: {
|
||||
lat: Math.max(areaA.sw.lat, areaB.sw.lat),
|
||||
lng: Math.max(areaA.sw.lng, areaB.sw.lng)
|
||||
},
|
||||
ne: {
|
||||
lat: Math.min(areaA.ne.lat, areaB.ne.lat),
|
||||
lng: Math.min(areaA.ne.lng, areaB.ne.lng)
|
||||
}
|
||||
};
|
||||
}
|
||||
Ox.forEach(Ox.sub(areas, 1), function(parts) {
|
||||
if (ret.length == 1 && parts.length == 1) {
|
||||
ret = intersect(ret[0], parts[0]);
|
||||
} else {
|
||||
// intersect each part of the intersection
|
||||
// with all parts of the next area
|
||||
intersections = Ox.compact(ret.map(function(part) {
|
||||
return Ox.intersectAreas(Ox.merge(part, parts));
|
||||
}));
|
||||
ret = intersections.length == 0 ? null
|
||||
: Ox.joinAreas(intersections);
|
||||
}
|
||||
if (ret === null) {
|
||||
return false;
|
||||
} else {
|
||||
ret = splitArea(ret);
|
||||
}
|
||||
});
|
||||
return ret ? Ox.joinAreas(ret) : null;
|
||||
};
|
||||
|
||||
/*@
|
||||
Ox.joinAreas <f> Joins an array of areas
|
||||
<script>
|
||||
Ox.test.areas = [
|
||||
{sw: {lat: -30, lng: 150}, ne: {lat: -20, lng: 160}},
|
||||
{sw: {lat: -10, lng: 170}, ne: {lat: 10, lng: -170}},
|
||||
{sw: {lat: 20, lng: -160}, ne: {lat: 30, lng: -150}}
|
||||
];
|
||||
</script>
|
||||
> Ox.joinAreas(Ox.test.areas)
|
||||
{sw: {lat: -30, lng: 150}, ne: {lat: 30, lng: -150}}
|
||||
@*/
|
||||
Ox.joinAreas = function(areas) {
|
||||
// While the combined latitude is trivial (min to max), the combined longitude
|
||||
// spans from the eastern to the western edge of the largest gap between areas
|
||||
var ret = areas[0],
|
||||
gaps = [{
|
||||
sw: {lat: -90, lng: ret.ne.lng},
|
||||
ne: {lat: 90, lng: ret.sw.lng}
|
||||
}];
|
||||
function containsGaps(area) {
|
||||
return Ox.map(gaps, function(gap, i) {
|
||||
return Ox.containsArea({
|
||||
sw: {lat: -90, lng: area.sw.lng},
|
||||
ne: {lat: 90, lng: area.ne.lng}
|
||||
}, gap) ? i : null;
|
||||
});
|
||||
}
|
||||
function intersectsWithGaps(area) {
|
||||
var ret = {};
|
||||
gaps.forEach(function(gap, i) {
|
||||
var intersection = Ox.intersectAreas([area, gap]);
|
||||
if (intersection) {
|
||||
ret[i] = intersection;
|
||||
}
|
||||
});
|
||||
return ret;
|
||||
}
|
||||
function isContainedInGap(area) {
|
||||
var ret = -1;
|
||||
Ox.forEach(gaps, function(gap, i) {
|
||||
if (Ox.containsArea(gap, area)) {
|
||||
ret = i;
|
||||
return false;
|
||||
}
|
||||
});
|
||||
return ret;
|
||||
}
|
||||
Ox.sub(areas, 1).forEach(function(area) {
|
||||
var index, indices, intersections;
|
||||
if (area.sw.lat < ret.sw.lat) {
|
||||
ret.sw.lat = area.sw.lat;
|
||||
}
|
||||
if (area.ne.lat > ret.ne.lat) {
|
||||
ret.ne.lat = area.ne.lat;
|
||||
}
|
||||
// If the area is contained in a gap, split the gap in two
|
||||
index = isContainedInGap(area);
|
||||
if (index > -1) {
|
||||
gaps.push({
|
||||
sw: gaps[index].sw,
|
||||
ne: {lat: 90, lng: area.sw.lng}
|
||||
});
|
||||
gaps.push({
|
||||
sw: {lat: -90, lng: area.ne.lng},
|
||||
ne: gaps[index].ne
|
||||
});
|
||||
gaps.splice(index, 1);
|
||||
} else {
|
||||
// If the area contains gaps, remove them
|
||||
indices = containsGaps(area);
|
||||
Ox.reverse(indices).forEach(function(index) {
|
||||
gaps.splice(index, 1);
|
||||
});
|
||||
// If the area intersects with gaps, shrink them
|
||||
intersections = intersectsWithGaps(area);
|
||||
Ox.forEach(intersections, function(intersection, index) {
|
||||
gaps[index] = {
|
||||
sw: {
|
||||
lat: -90,
|
||||
lng: gaps[index].sw.lng == intersection.sw.lng
|
||||
? intersection.ne.lng : gaps[index].sw.lng
|
||||
},
|
||||
ne: {
|
||||
lat: 90,
|
||||
lng: gaps[index].ne.lng == intersection.ne.lng
|
||||
? intersection.sw.lng : gaps[index].ne.lng
|
||||
}
|
||||
};
|
||||
});
|
||||
}
|
||||
});
|
||||
if (gaps.length == 0) {
|
||||
ret.sw.lng = -180;
|
||||
ret.ne.lng = 180;
|
||||
} else {
|
||||
gaps.sort(function(a, b) {
|
||||
return (
|
||||
b.ne.lng
|
||||
+ (Ox.crossesDateline(b.sw, b.ne) ? 360 : 0)
|
||||
- b.sw.lng
|
||||
) - (
|
||||
a.ne.lng
|
||||
+ (Ox.crossesDateline(a.sw, a.ne) ? 360 : 0)
|
||||
- a.sw.lng
|
||||
);
|
||||
});
|
||||
ret.sw.lng = gaps[0].ne.lng;
|
||||
ret.ne.lng = gaps[0].sw.lng;
|
||||
}
|
||||
return ret;
|
||||
};
|
||||
|
||||
}());
|
||||
|
||||
//@ Ox.Line <f> (undocumented)
|
||||
|
|
Loading…
Reference in a new issue