More

Intersecting lines to get crossings using Python with QGIS?


I have a set of lines representing bus lines. Some of the lines are overlapping and take the same roads.

I am able to extract the nodes.

However I am interested in extracting only crossings like this:

How can I do this? I am looking for ways with QGIS or Python.

I tried the intersection method from GDAL Python but this basically returns me only the vertices.

The Line Intersections method from QGIS returns me the crossings if two lines cross. However in the case that two bus lines go far part of their route on the same road, it doesn't give me they point where they merge.


The nodes:

You want two things, the end points of the polylines (without intermediate nodes) and the intersection points. There are an additional problem, some polylines end points are also intersection points:

A solution is to use Python and the modules Shapely and Fiona

1) Read the shapefile:

from shapely.geometry import Point, shape import fiona lines = [shape(line['geometry']) for line in fiona.open("your_shapefile.shp")]

2) Find the end Points of the lines (how would one get the end points of a polyline?):

endpts = [(Point(list(line.coords)[0]), Point(list(line.coords)[-1])) for line in lines] # flatten the resulting list to a simple list of points endpts= [pt for sublist in endpts for pt in sublist]

3) Compute the intersections (iterating through pairs of geometries in the layer with the itertools module). The result of some intersections are MultiPoints and we want a list of points:

import itertools inters = [] for line1,line2 in itertools.combinations(lines, 2): if line1.intersects(line2): inter = line1.intersection(line2) if "Point" == inter.type: inters.append(inter) elif "MultiPoint" == inter.type: inters.extend([pt for pt in inter]) elif "MultiLineString" == inter.type: multiLine = [line for line in inter] first_coords = multiLine[0].coords[0] last_coords = multiLine[len(multiLine)-1].coords[1] inters.append(Point(first_coords[0], first_coords[1])) inters.append(Point(last_coords[0], last_coords[1])) elif "GeometryCollection" == inter.type: for geom in inter: if "Point" == geom.type: inters.append(geom) elif "MultiPoint" == geom.type: inters.extend([pt for pt in geom]) elif "MultiLineString" == geom.type: multiLine = [line for line in geom] first_coords = multiLine[0].coords[0] last_coords = multiLine[len(multiLine)-1].coords[1] inters.append(Point(first_coords[0], first_coords[1])) inters.append(Point(last_coords[0], last_coords[1]))

4) Eliminate duplicates between end points and intersection points (as you can see in the figures)

result = endpts.extend([pt for pt in inters if pt not in endpts])

5) Save the resulting shapefile

from shapely.geometry import mapping # schema of the shapefile schema = {'geometry': 'Point','properties': {'test': 'int'}} # creation of the shapefile with fiona.open('result.shp','w','ESRI Shapefile', schema) as output: for i, pt in enumerate(result): output.write({'geometry':mapping(pt), 'properties':{'test':i}})

Final result:

The line segments

If you want also the segments between the nodes, you need to "planarize" (Planar Graph, no edges cross each other) your shapefile. This can be done by the unary_union function of Shapely

from shapely.ops import unary_union graph = unary_union(lines)


Computing intersections of a Polyline and a line

I wrote a script to compute / interpolate the nodes of a Polyline at a given latitude, input_lat . It works for my purpose but I am wondering if there is a better, more optimized way of doing this.

The idea is to first check if there is any exact match, i.e. if there are nodes at the exact given latitude so I subtract the input latitude to the list of nodes (the latitudes list) and if there are any zeros, I can just read the longitude of these.

Most of the time the crossing will be between 2 consecutive nodes, with one being below (or above) and the next one being on the other side of the input_lat (the node above will retain a positive value after subtracting input_lat and the node below a negative value) so in my latitudes list I detect that by checking for a change of sign between two consecutive elements, so I multiply each element with the next: if the result is negative, there is a change of sign.
I do this with np.multiply(latitudes[1:], latitudes[:-1]) but I wonder if there is a better way.

The script is meant to run from within QGIS so all the qgis.* libraries that are imported and the Qgs commands are specific to QGIS.


Osmnx¶

This week we will explore a new and exciting Python module called osmnx that can be used to retrieve, construct, analyze, and visualize street networks from OpenStreetMap. In short it offers really handy functions to download data from OpenStreet map, analyze the properties of the OSM street networks, and conduct network routing based on walking, cycling or driving.

There is also a scientific article available describing the package:

  • Boeing, G. 2017. “OSMnx: New Methods for Acquiring, Constructing, Analyzing, and Visualizing Complex Street Networks.” Computers, Environment and Urban Systems 65, 126-139. doi:10.1016/j.compenvurbsys.2017.05.004

You could try low-pass filtering the input signal to get smoother zero-crossings (or even band-pass filtering if you have a good idea of the frequency location of the sine wave). The risk is that if sample-accurate phase information is essential to your application, the additional lag from the filter might be a problem.

Another approach: instead of trying to transform the sine wave into a square wave, what about getting an independent square wave oscillator to align itself in phase/frequency with the sine wave? This can be done with a phase-locked loop.

What you've shown certainly is a zero-crossing detector. A couple things come to mind that might improve your situation:

If you have noise that is outside the band of your signal (which is almost certainly the case, since your input is a pure tone), then you can improve the signal-to-noise ratio by applying a bandpass filter around the signal of interest. The passband width of the filter should be chosen based on how precisely you know the sinusoid frequency a priori. By reducing the amount of noise present on the sinusoid, the number of false zero crossings and their jitter about the correct crossing times will be reduced.

  • As a side note, if you don't have good information ahead of time, you could use a more sophisticated technique known as an adaptive line enhancer, which, as its name implies, is an adaptive filter that will enhance a periodic input signal. However, this is a somewhat advanced topic, and you typically have a good enough idea of your signal's frequency that this sort of approach isn't needed.

With respect to the zero-crossing detector itself, you might add some hysteresis to the process. This would prevent the generation of extra spurious measured crossings around the correct crossing instant. Adding hysteresis to the detector might look something like this:

Effectively, you add some state to your zero-crossing detector. If you believe the input signal to have a positive value, you require that the signal dip down below a chosen threshold value -T in order to declare a real zero crossing. Likewise, you require that the signal rise back up above the threshold T in order to declare that the signal has oscillated back to positive again.

You could choose the thresholds to be whatever you want, but for a balanced signal like a sinusoid, it makes sense to have them be symmetric about zero. This approach can help give you a cleaner-looking output, but it will add some time delay due to the fact that you're actually measuring non-zero threshold crossings instead of zero crossings.

As pichenettes suggested in his answer, a phase-locked loop would be most likely the best way to go, as a PLL does pretty much exactly what you're trying to do. In short, you run a square wave generator that runs in parallel with the input sinusoid. The PLL makes periodic phase measurements on the sinusoid, then filters that stream of measurements in order to steer the instantaneous frequency of the square wave generator. At some point, the loop will (hopefully) lock, at which point the square wave should be locked in frequency and phase with the sinusoid of the input (with some amount of error, of course nothing in engineering is perfect).


Syntax

The input dual-line features, such as road casings, from which centerlines are derived.

The output feature class to be created.

Sets the maximum width of the dual-line features to derive centerline. A value must be specified, and it must be greater than zero. You can choose a preferred unit the default is the feature unit.

Sets the minimum width of the dual-line features to derive centerline. The minimum width must be greater than or equal to zero, and it must be less than the maximum width. The default value is zero. You can specify a preferred unit the default is the feature unit.


2 Answers 2

Regarding your question, I believe if you try crossing your z-axes, $z_1 imes z_2$, you'll get the x-axis you're looking for (orthogonal to both axes). Because the two z axes intersect, your $r$ or $a$ value, depending on your notation, will be zero because the new x-axis lies on your previous z-axis.

The right hand rule will help you here. For a given joint i: your thumb will correspond to the Zi-1 axis, your index will correspond to the Zi axis and your middle finger will correspond to Xi axis.


2 Answers 2

There's a long winded way with TimeSeriesThread or interpolation and subtracting the curves to find zero crossings, however there's also a shortcut where you can extract the lines from the plot and immediately get the intersections:

. and if you're okay with using undocumented functions you could alternatively write:

Find where the differences between two time series change sign by subtracting the second series from the first. The transitions show the dates where the two series intersect.

None of the intersecting values are equal (none of the differences are zero).

Find the dates before and after each transition value. Get the values for each date from the smoothed values. Plot the dates with the smoothed data plot.

Here are the dates before and after each intersection between the time series.


6 Answers 6

[I gave a similar response some time ago either in StackOverflow or MSE but now I cannot find it.]

One way is to track the solution to the ODE that runs over the difference if[x]-g[x] . Use WhenEvent to record axis crossings. This will find all zeros that do not have multiplicity (that is, that cross transversally). Should also find any that are of odd multiplicity since they still cross the axis, albeit at slope of zero.

I take it that the essential question is how to solve an equation in which a term involves an InterpolatingFunction . If it is merely to plot the points, then I would use andre's method. Otherwise, I would use an approach like Daniel Lichtblau's with a small modification. The rest of this is essentially an extended comment to Daniel's answer and george2079's comment to it.

NDSolve is particularly well-suited to this task. The reasons are manifold. First, the process of integration runs over the whole interval and is likely to discover all the roots where a sign change occurs. Second, root-finding algorithms are invoked when an event in WhenEvent is detected. Further the initial condition for an event will be close to the root and the root-finding algorithms will converge relatively quickly, so quickly that in the OP's example the time it took NDSolve to find all of the roots was about the same as using FindRoot on each of them. So NDSolve is being used to track the function and invoke Finally, if the function is highly oscillatory, NDSolve will adapt its step size so that zero-crossings are not (likely to be) missed.

You get more accurate results if the equation to be solved is passed to WhenEvent instead of y[x] == 0 . It seems an important principle, even if the interpolating function is only approximate. The symbol y[x] represents another layer of approximation it seems clearer to use the actual equation whose solution is desired. It takes slightly less time if you ask that no solution be returned. In the example below I added an oscillatory component.

Using the actual equation instead of y[x] produces more accurate results:


2 Answers 2

I also receive the same when opening .shp files directly and selecting qgis-bin as the program to use. But I always load QGIS via the Desktop icon. I'm not exactly sure why but my guess is that the Desktop icon simultaneously runs the following 2 files in order to load QGIS (this is the Target which you can see in the icon's properties):


"C:Program FilesQGIS Valmierabinnircmd.exe" exec hide C:PROGRA

My advice is to load QGIS via the Desktop icon (usually has a name like "QGIS Desktop 2.4.0") and then drag/drop your shapefile that way.

As Steve has already found out, it's possible to drag your shapefile to the "qgis.bat" file which will load QGIS and the shapefile itself.

Joseph, this works here! I've now also re-mapped .shp files directly to the qgis.bat so that I can click them directly.

Brilliant! You just taught me something also :)

Mapping the shapefile to QGIS is awesome.

@Branco, certainly is buddy! Only been 9 months since I first started using GIS software of any kind (I chose QGIS) and still there's masses of things for me to learn :)

I had this issue recently. None of the answers here worked to me.

When I use QGIS 2.4, I usually work with my laptop plugged in a bigger lcd monitor. Then, I project everything in the second monitor using a hdmi adapater. So, when I turned off the second monitor, QGIS started to show the message 'qgis_core.dll missing'.

To fix, I pressed the key 'Fn + F1' (Dell laptop) and choose the first option to project images only to the first monitor. Than, the QGIS worked again.


5 Answers 5

The only reason that two lines will not intersect is if they are parallel. Being parallel is the same as having the same slope. So, in your example, line $1$ has slope

Since these two numbers are not the same, the lines are not parallel, and they intersect somewhere.

Now, if what you are considering is only the line segment between the two points, I would first consider the slopes as we just did. If they are parallel, then for sure you know that they do not intersect. If, however, the slopes are different, then they will intersect in one point. You then need to find this point, and see if it happens in bounds given on the $x$-values.

So, when you find that the lines intersect at the point $(35, 19.5)$, then this intersection is outside the bounds given, since, e.g., your first line only goes from $x = 15$ to $x = 30$. It never reaches $x = 35$.

I'm assuming you can already check intersection of lines, as that's already been answered and you seem to understand it fine.

The segments are $AB$ and $CD$. They intersect if their intersection point lies within the darker middle rectangle (i.e. the area in space that they both occupy). In other words, if the intersection point is $(x,y)$, then $x$ must be less than the smallest right-side value (the $x$-coordinate of $AH$ here), and larger than the smallest left-side value ($GB$). Here the check $x>GB_x$ fails, so they don't intersect. The idea is similar for $y$ values, and it has to pass all 4 tests (two for $x$ and two for $y$) before you can conclude that they intersect.

There are two cases to consider when determining if two line segments $AB$ and $CD$ intersect: (1) The line segments are not co-linear (top three images in the following figure) (2) the line segments are co-linear (bottom two images).

The standard $y = mx + b$ is not generally useful as it omits vertical lines. Here it is best to consider the following implicit function $h(P)$ for a line passing through $A$ and $B:$ $ h(P) = (B - A) imes (P - A)= 0 $ where $U imes V = U_x cdot V_y - U_y cdot V_x.$ Note that $h(P)$ defines a half space by determining where the point $P$ lies relative to the boundary line through $AB:$ $ egin h(P) > 0 & mbox <$P$ in positive half-space> h(P) = 0 & mbox <$P$ on the line> h(P) < 0 & mbox <$P$ in negative half-space>end $ Thus we know if the points $C$ and $D$ straddle the (infinite) line through $AB$ if both $h(C)$ and $h(D)$ are non-zero and have opposite signs. In the general case, we know the line segments $AB$ and $CD$ intersect if the points $C$ and $D$ straddle the line through $AB$ and the points $A$ and $B$ straddle the line through $CD.$

First we must handle the co-linear case where $h(C) = 0$ and $h(D) = 0.$ Here we have an intersection iff $ min(C_x,D_x) leq max(A_x,B_x) wedge max(C_x,D_x) geq min(A_x,B_x) $ and $ min(C_y,D_y) leq max(A_y,B_y) wedge max(C_y,D_y) geq min(A_y,B_y) $

Otherwise, in the general case we use our half space equations. The half space $g(x)$ defined by $CD$ is $ g(P) = (D - C) imes (P - C) = 0. $ We have an intersection where $ (h(C) cdot h(D) leq 0) wedge (g(A) cdot g(B) leq 0). $

If you wish to know the actual intersection point you plug the parametric equation $L(t)$ for a line through $AB$ $ L(t) = (B - A)cdot t + A, -infty < t < infty $ into $g$ and solve for $t:$ $ g(L(t)) = (D - C) imes (L(t) - C) = 0 (D - C) imes ((B - A)cdot t + A - C) = 0 t = frac<(C - A) imes (D - C)> <(D - C) imes (B - A)>$ Plug this value for $t$ back into $L(t)$ and you have your intersection point. Of course the assumes the caveat that $(D - C) imes (B - A) eq 0$ where the lines are not parallel.


Watch the video: QGIS Python - Use Processing Tools in a Python Script (October 2021).