Top 5 Recent Articles
- Algorithms (15)
- All (401)
- Biography (1)
- Blog (44)
- Business Requirements (1)
- Commentary (1)
- Customers (2)
- Data Models (1)
- Education (2)
- GeoRaptor (13)
- Image Processing (2)
- Import Export (8)
- Licensing (2)
- Linear Referencing (4)
- Manifold GIS (3)
- Mapping (1)
- MySQL Spatial (7)
- Networking and Routing (including Optimization) (3)
- Open Source (18)
- Oracle Spatial and Locator (192)
- PostGIS (34)
- Published Articles (1)
- Recommendations (1)
- Services (1)
- Software Change Log (1)
- Source Code (35)
- Space Curves (9)
- Spatial Database Functions (104)
- Spatial DB comparison (1)
- Spatial XML Processing (10)
- SQL Server Spatial (General) (83)
- SQL Server Spatial (LRS) (38)
- Standards (1)
- Stored Procedure (15)
- Tessellation or Gridding (9)
- Tools (2)
- Topological Relationships (1)
- Training (2)
Using Range Querying to Filter LineString Segments
There are many types of ranged values used to describe business objects.
Here are a few:
- Date Range: Date valid from and date valid to. This may describe the date an object was first made available to date on which it was retired.
- Numeric Range: An example in the GIS world is the range of numeric value associated with a measured linear geometry; another the range of temperatures recorded at a weather station.
In these situations, as in others like it, a common query is to find values with a specific range.
“Find me the business objects that were in operation between 1st January 2017 to 30th June 2017”.
“Find me the road segment whose measures fall within a certain range”.
In all cases, the query approach is the same. The predicate that will do all out “heavy lifting” in all ranged data applications is:
WHERE Greatest(f.date_from, f.query_date_from) < Least(f.date_to, f.query_date_to);
This predicate will find data that has an overlap of actual data ranges. If one wants to return ranges that have an end/start (query windows touches start at its end) or start /end (query window touches end at its start), one simply uses less than or equal:
Filtering linestring segments as in the roads example above is easier than you think.
I will say little about the following example, but it is a powerful reminder that generic IT processing concepts can be applied to spatial data: one does not need a GIS to implement this sort of processing, but it helps with the visualisation.
In the following we use the complete length of the linestring and the component lengths of its segments to execute our querying. First off I break the linestring into its components. Then I fabricate “measures” using the cumulative length fro the start of the linestring to generate lo and hi measure values. I then generate 10 ranges using the original linestring, query the segments and then classify the relationships.
(I do the processing here using SQL Server Spatial. SQL Server does not have a GREATEST/LEAST operators so the predicate is coded using case statements. The concepts can be implemented in any of the spatial databases.)
uWITH data as ( select geometry::STGeomFromText('LINESTRING(-1 -1,0 1, 2 2, 5 4,9 1)',0) as line ), segments_with_range AS ( SELECT segment_id, ROUND(COALESCE(LAG(f.length_hi,1) over (order by segment_id),0),4) as length_lo, ROUND(f.length_hi,4) as length_hi, f.segment FROM (SELECT gs.id segment_id, gs.geom as segment, SUM(gs.geom.STLength()) over (order by gs.id) as length_hi FROM data as a cross apply [dbo].[STSegmentize](a.line) as gs ) as f ) -- select * from segments_with_range; , range_query_data as ( select row_number() over (order by (SELECT 1)) as range_id, ROUND(g.lo_range,4) as lo_range, ROUND(g.lo_range + dbo.STRandomBetween(0,g.lLength) ,4) as hi_range from (select dbo.STRandomBetween(-1,d.line.STLength()+1) as lo_range, d.line.STLength() as lLength from data as d cross apply dbo.Generate_Series(1,10,1) as range ) as g ) -- select * from range_query_data; SELECT segment_id, range_id, CASE WHEN length_lo = lo_range and length_hi = hi_range THEN 'EQUAL' WHEN lo_range > length_lo and hi_range < length_hi THEN 'INSIDE' WHEN hi_range < length_lo and lo_range > length_hi THEN 'DISJOINT' WHEN lo_range = length_lo THEN 'LEFT_START_TOUCH' WHEN hi_range = length_hi THEN 'RIGHT_END_TOUCH' WHEN lo_range = length_hi THEN 'LEFT_TOUCH_END' WHEN hi_range = length_lo THEN 'RIGHT_TOUCH_START' WHEN lo_range < length_lo and hi_range > length_hi THEN 'CONTAINS' WHEN lo_range < length_hi and hi_range > length_hi OR lo_range < length_lo and hi_range > length_lo THEN 'COVERS' ELSE 'UNKNOWN' END AS Description, length_lo, lo_range, hi_range, length_hi, swd.segment.STAsText() as segment FROM range_query_data as rqd, segments_with_range as swd WHERE case when swd.length_lo > rqd.lo_range then swd.length_lo else rqd.lo_range end < case when swd.length_hi < rqd.hi_range then swd.length_hi else rqd.hi_range end;
|1||3||COVERS||0||0.2058||11.1827||2.2361||LINESTRING (-1 -1, 0 1)|
|1||5||COVERS||0||1.7714||3.9704||2.2361||LINESTRING (-1 -1, 0 1)|
|1||7||COVERS||0||1.9964||13.993||2.2361||LINESTRING (-1 -1, 0 1)|
|2||1||CONTAINS||2.2361||1.9425||13.0086||4.4721||LINESTRING (0 1, 2 2)|
|2||5||COVERS||2.2361||1.2624||3.8725||4.4721||LINESTRING (0 1, 2 2)|
|3||1||CONTAINS||4.4721||4.4405||14.1694||8.0777||LINESTRING (2 2, 5 4)|
|3||3||COVERS||4.4721||4.9171||13.611||8.0777||LINESTRING (2 2, 5 4)|
|3||7||INSIDE||4.4721||4.6113||7.2309||8.0777||LINESTRING (2 2, 5 4)|
|3||8||COVERS||4.4721||7.9161||9.5631||8.0777||LINESTRING (2 2, 5 4)|
|3||9||COVERS||4.4721||6.2317||10.6425||8.0777||LINESTRING (2 2, 5 4)|
|3||10||INSIDE||4.4721||4.93||6.2276||8.0777||LINESTRING (2 2, 5 4)|
|4||2||COVERS||8.0777||12.4585||18.949||13.0777||LINESTRING (5 4, 9 1)|
|4||3||COVERS||8.0777||9.5043||20.1909||13.0777||LINESTRING (5 4, 9 1)|
|4||4||CONTAINS||8.0777||7.3554||16.9418||13.0777||LINESTRING (5 4, 9 1)|
|4||5||CONTAINS||8.0777||3.7772||14.5736||13.0777||LINESTRING (5 4, 9 1)|
|4||6||COVERS||8.0777||9.2214||14.2315||13.0777||LINESTRING (5 4, 9 1)|
|4||7||COVERS||8.0777||2.0765||10.8413||13.0777||LINESTRING (5 4, 9 1)|
|4||8||COVERS||8.0777||5.5982||9.7304||13.0777||LINESTRING (5 4, 9 1)|
|4||9||CONTAINS||8.0777||2.7664||13.1508||13.0777||LINESTRING (5 4, 9 1)|
|4||10||COVERS||8.0777||-0.3048||9.1617||13.0777||LINESTRING (5 4, 9 1)|
This sort of processing is embedded in my TSQL code, in particular the STSegmentize() function.
I hope this is of interest to someone.