I have a table which contains a number of geometries. I am attempting to extract the one which is most covered by another geometry.
This is best explained with pictures and code.
Currently I am doing this simple spatial query to get any rows that spatially interact with a passed in WKT Geometry
SELECT ID, NAME FROM MY_TABLE WHERE
sdo_anyinteract(geom,
sdo_geometry('POLYGON((400969 95600,402385 95957,402446 95579,400905 95353,400969 95600))',27700)) = 'TRUE';
Works great, returns a bunch of rows that interact in any way with my passed in geometry.
What I preferably want though is to find which one is covered most by my passed in geometry. Consider this image.
The coloured blocks represent 'MY_TABLE'. The black polygon over the top represents my passed in geometry I am searching with. The result I want returned from this is Polygon 2, as this is the one that is most covered by my polygon. Is this possible? Is there something I can use to pull the cover percentage in and order by that or a way of doing it that simply returns just that one result?
--EDIT--
Just to supplement the accepted answer (which you should go down and give an upvote as it is the entire basis for this) this is what I ended up with.
SELECT name, MI_PRINX,
SDO_GEOM.SDO_AREA(
SDO_GEOM.SDO_INTERSECTION(
GEOM,
sdo_geometry('POLYGON((400969.48717156524 95600.59583240788,402385.9445972018 95957.22742049221,402446.64806962677 95579.91508788493,400905.95874489535 95353.03765349534,400969.48717156524 95600.59583240788))',27700)
,0.005
)
,0.005) AS intersect_area
FROM LIFE_HEATHLAND WHERE sdo_anyinteract(geom, sdo_geometry('POLYGON((400969.48717156524 95600.59583240788,402385.9445972018 95957.22742049221,402446.64806962677 95579.91508788493,400905.95874489535 95353.03765349534,400969.48717156524 95600.59583240788))',27700)) = 'TRUE'
ORDER BY INTERSECT_AREA DESC;
This returns me all the results that intersect my query polygon with a new column called INTERSECT_AREA, which provides the area. I can then sort this and pick up the highest number.
Just compute the intersection between each of the returned geometries and your query window (using SDO_GEOM.SDO_INTERSECTION()), compute the area of each such intersection (using SDO_GEOM.SDO_AREA()) and return the row with the largest area (order the results in descending order of the computed area and only retain the first row).
For example, the following computes how much space Yellowstone National Park occupies in each state it covers. The results are ordered by area (descending).
SELECT s.state,
sdo_geom.sdo_area (
sdo_geom.sdo_intersection (
s.geom, p.geom, 0.5),
0.5, 'unit=sq_km') area
FROM us_states s, us_parks p
WHERE SDO_ANYINTERACT (s.geom, p.geom) = 'TRUE'
AND p.name = 'Yellowstone NP'
ORDER by area desc;
Which returns:
STATE AREA
------------------------------ ----------
Wyoming 8100.64988
Montana 640.277886
Idaho 154.657145
3 rows selected.
To only retain the row with the largest intersection do:
SELECT * FROM (
SELECT s.state,
sdo_geom.sdo_area (
sdo_geom.sdo_intersection (
s.geom, p.geom, 0.5),
0.5, 'unit=sq_km') area
FROM us_states s, us_parks p
WHERE SDO_ANYINTERACT (s.geom, p.geom) = 'TRUE'
AND p.name = 'Yellowstone NP'
ORDER by area desc
)
WHERE rownum = 1;
giving:
STATE AREA
------------------------------ ----------
Wyoming 8100.64988
1 row selected.
The following variant also returns the percentage of the park's surface in each intersecting state:
WITH p AS (
SELECT s.state,
sdo_geom.sdo_area (
sdo_geom.sdo_intersection (
s.geom, p.geom, 0.5),
0.5, 'unit=sq_km') area
FROM us_states s, us_parks p
WHERE SDO_ANYINTERACT (s.geom, p.geom) = 'TRUE'
AND p.name = 'Yellowstone NP'
)
SELECT state, area,
RATIO_TO_REPORT(area) OVER () * 100 AS pct
FROM p
ORDER BY pct DESC;
If you want to return the geometry of the intersections, just include that into your result set.
Related
I'm essentially looking for a way to normalise (t/t0) a bunch of measurements across timepoints with a specific timepoint, e.g. timepoint=0.
I have a table as following:
coordinate,timepoint,quantity
A1,0,50
B2,0,10
C3,0,60
A2,0,20
F1,0,20
A1,1,100
B2,1,150
C3,1,120
A2,1,140
F1,1,160
A1,4,100
B2,4,80
C3,4,80
A2,4,100
F1,4,120
I want to make a table that divides all the other non-zero timepoint rows by the 0 timepoint rows where the coordinates match, i.e. A1-t1 / A1-t0, A1-t4 / A1-t0, B2-t1 / B2-t0, B2-t0 / B2-t4 etc. etc. for wherever there is a join on coordinate available.
The result would be like:
coordinate,timepoint,quantity
A1,0,1
B2,0,1
C3,0,1
A2,0,1
F1,0,1
A1,1,2
B2,1,15
C3,1,2
A2,1,7
F1,1,8
etc.
Something like this mostly works...
select t0.coordinate,t0.quantity,tother.quantity,tother.quantity/t0.quantity as tnorm
(select * from table
where timepoint != 0) as tother
LEFT JOIN (select * from table
where timepoint = 0) as t0
ON (t1.coordinate = t2.coordinate);
Though I ideally would like to have a pivot of the table could be displayed where each column is each normalisation, e.g. columns as
coordinate, t0/t0, t1/t0, t4/t0 etc.
A1,1,2,value etc.
B2,1,15,value etc.
C3,1,2,value etc.
A2,1,7,value etc.
F1,1,8,value etc.
...though this might not be possible and must be done in postprocessing (e.g. pandas pivot).
I couldn't work out the right syntax for this one - any help is appreciated.
WITH t1 AS (
SELECT position, quantity
FROM table
WHERE timepoint = 0
)
SELECT t2.position, t2.timepoint, (t2.quantity/t1.quantity) quantity
FROM table t2
INNER JOIN t1 ON t2.position=t1.position
I have created a hierarchy table in my SQL Server. I have a column hierarchyId. Each level of hierarchy represent a geographical/political level of a country:
Countries
Regions
Provinces
For each row I can fill some boundaries or not. To simplify I replace in the example the geometry column with a bit column.
I need to get lowest level that has boundaries filled. If at least one child has boundaries, surely also parent has boundaries.
I make an example:
For example, I have that tree. In my query I should get green and red areas. In this moment I get only green areas.. so, I should get:
Valle d'aosta because is the lowest level and it has boundaries (and that is OK);
Milano and Brescia because they are the lowest level with boundaries. I should not get Bergamo because it has no boundaries, but I should get Lombardia instead of Bergamo;
Italy because both Piemonte and Torino have no boundaries;
Lazio because Roma has no boundaries.
My query is partially correct.. I get all lowest levels. But I do not get the minimum high level that respect my condition..
I share a link with the example: http://sqlfiddle.com/#!18/878577/1
Here also the query you can see in sql fiddler:
select * from country_subdivisions cs
where IsoCode IN(
select cs.IsoCode
from parent p
where cs.Level.IsDescendantOf(p.Level) = 1
and p.CountryISOAlpha2Code = cs.CountryISOAlpha2Code
-- and (cs.Level.GetLevel() = p.Level.GetLevel() + 1 or cs.Level.GetLevel() = p.Level.GetLevel())
and cs.Level.GetLevel() = (SELECT MAX(leaf.Level.GetLevel()) FROM country_subdivisions leaf WHERE leaf.Level.IsDescendantOf(cs.Level) = 1 and leaf.HasBoundaries = 1)
)
As you can see I correctly get the green areas but not the red one.
Any Idea? Do I have been clear?
Thank you
I think the logic is summarized as follows:
Return a parent when:
That parent has boundaries, and
Either:
it has no children, or
it has has at least one child that has no boundaries.
This could be formulated as follows:
select parent.*
from country_subdivisions parent
where parent.HasBoundaries = 1
and 0 < (select case
when count(*) = 0 then 1
else count(case when child.HasBoundaries = 0 then 1 end)
end
from country_subdivisions child
where child.Level.GetAncestor(1) = parent.Level
);
I have the following code whick works fine:
select vissoort, count(1), ST_Buffer(ST_GeomFromText('POINT(5.341248 51.615590)',4326):: geography, 2500)
from visvangsten
where st_intersects(visvangsten.locatie,
ST_Buffer(ST_GeomFromText('POINT(5.3412480 51.615590)',4326):: geography, 2500))
group by vissoort
order by 2 desc
Now I want the same function but then selecting the features within a polygon instead of the circle/buffer.
I tried things like this but nothing worked:
select vissoort, count(1), ST_asText( ST_Polygon('LINESTRING(5.303 51.629, 5.387 51.626, 5.393 51.588, 5.281 51.592)'::geometry, 4326) )
from visvangsten
where st_contains(ST_asText( ST_Polygon('LINESTRING(5.303 51.629, 5.387 51.626, 5.393 51.588, 5.281 51.592)'::geometry, 4326) ), visvangsten.locatie);
group by vissoort
order by 2 desc limit 1
The database table looks like this:
id ([PK]bigint)
datum(date)
vissoort(character varying)
locatie(geometry)
15729
2007-06-23
Blankvoorn
0101000...etc.
etc.
etc.
etc.
etc.
Does someone know the answer?
Keep in mind that to transform a LineString into a Polygon you need to have a closed ring - in other words, the first and last coordinate pairs must be identical. That being said, you can convert a LineString into a Polygon using the function ST_MakePolygon. The following example is probably what you're looking for:
WITH j (geom) AS (
VALUES
(ST_MakePolygon('SRID=4326;LINESTRING(-4.59 54.19,-4.55 54.23,-4.52 54.19,-4.59 54.19)'::geometry)),
(ST_Buffer('SRID=4326;LINESTRING(-4.59 54.19,-4.55 54.23,-4.52 54.19,-4.59 54.19)'::geometry,0.1))
)
SELECT ST_Contains(geom,'SRID=4326;POINT(-4.5541 54.2043)'::geometry) FROM j;
st_contains
-------------
t
t
(2 Zeilen)
I want the equivalent of ST_EXTENT or ST_ENVELOPE in BigQuery, but I can't find a way to make this query run:
SELECT REGEXP_EXTRACT(name, ', (..)') state
, ST_EXTENT(ARRAY_AGG(urban_area_geom)) corners
, COUNT(*) cities
FROM `bigquery-public-data.geo_us_boundaries.urban_areas`
GROUP BY state
The desired result of this query is a list of bounding boxes to cover all urban areas around the US, grouped by state.
I created a feature request to get a native implementation of ST_EXTENT(). Please add your votes and evidence of why you need this function so the team can prioritize and keep you informed of any developments:
https://issuetracker.google.com/issues/148915449
In the meantime, the best solution I can offer:
fhoffa.x.st_bounding_box(): a naive bounding box UDF.
Use it like this:
SELECT REGEXP_EXTRACT(name, ', (..)') state
, fhoffa.x.st_bounding_box(ARRAY_AGG(urban_area_geom)).polygon
, COUNT(*) urban_areas
FROM `bigquery-public-data.geo_us_boundaries.urban_areas`
GROUP BY state
The code behind it:
CREATE OR REPLACE FUNCTION fhoffa.x.st_bounding_box(arr ANY TYPE) AS ((
SELECT AS STRUCT *
, ST_MakePolygon(ST_GeogFromText(FORMAT('LINESTRING(%f %f,%f %f,%f %f,%f %f)',minlon,minlat,maxlon,minlat,maxlon,maxlat,minlon, maxlat))) polygon
FROM (
SELECT MIN(m.min_x) minlon, MAX(m.max_x) maxlon , MIN(m.min_y) minlat, MAX(m.max_y) maxlat
FROM (
SELECT
(SELECT AS STRUCT MIN(x) min_x, MAX(x) max_x, MIN(y) min_y, MAX(y) max_y FROM UNNEST(coords)) m
FROM (
SELECT ARRAY(
SELECT STRUCT(
CAST(SPLIT(c, ', ')[OFFSET(0)] AS FLOAT64) AS x,
CAST(SPLIT(c, ', ')[OFFSET(1)] AS FLOAT64) AS y
)
FROM UNNEST(REGEXP_EXTRACT_ALL(ST_ASGEOJSON(geog), r'\[([^[\]]*)\]')) c
) coords
FROM UNNEST(arr) geog
)
)
)
))
Notes:
Additional effort is needed to make it work with geometries that cross the -180 line.
Due to geodesic edges, the function result is not a true bounding box, i.e. ST_Covers(box, geom) might return FALSE.
In the picture above I'm not expecting each state to be fully covered, just its urban areas. So the bounding box is correct if there's no urban area in those uncovered corners.
The following polygon construction will give you exact "rectangles", but they become much more complex structures to work with.
ST_GEOGFROMGEOJSON(
FORMAT('{"type": "Polygon", "coordinates": [[[%f,%f],[%f,%f],[%f,%f],[%f,%f],[%f, %f]]]}'
, minlon,minlat,maxlon,minlat,maxlon,maxlat,minlon,maxlat,minlon,minlat)
)
I'll be looking forward to your comments and suggestions.
Since September 27, 2021 BigQuery support ST_BOUNDINGBOX and ST_EXTENT
I have the following dataset:
Each sales order line has an item which can be found in various location areas in our warehouse (UPPER, GROUND, FLOOR). What I want is a way to evaluate each sales order line and then pick one location, based on a condition.
The condition would say, if SO line contains a location with FLOOR, pick only that location, else check if it contains GROUND, then pick that, or if it contains neither ground or floor then return UPPER.
I don't want to see multiple location areas for each SO line. What's all the ways this can be done? I'd imagine some form of using a case statement with a HAVING clause?
This can be done using the row_number function by ordering the location areas based on the conditions.
select *
from (select t.*
,row_number() over(partition by so#
order by case when location_area='Floor' then 1
when location_area='GROUND' then 2
else 3 end) rn
from tablename t
) x
where rn = 1
Select coalesce(f.SO, g.SO, u.SO) SO,
coalesce(f.Line, g.Line, u.Line) Line,
coalesce(f.item_code, g.item_code, u.item_code) item_code,
coalesce(f.item_description, g.item_description, u.item_description) item_description,
coalesce(f.SO_Qty, g.SO_Qty, u.SO_Qty) SO_Qty,
coalesce(f.branch_Number, g.branch_Number, u.branch_Number) branch_Number,
coalesce(f.location_area, g.location_area, u.location_area) location_area
from myTable f
full join myTable g
on f.location_area='floor'
and g.SO = f.So
and g.location_area='ground'
full join myTable u
on u.SO = f.So
and u.location_area='upper'