4

As you can see in the screenshot, I have a HOUSENUM point geometry layer with the following data fields ID_HOUSENUM (corresponding to the portal text), ID_STREET (corresponding to the street code it belongs to) and ID_PARCEL (corresponding to the property it belongs to).

Each HOUSENUM is an individual point overlapped with each other.

enter image description here

Based on these data fields, how could we transfer the point geometries to the inner edge of each PARCEL taking into consideration the PARCEL it belongs to and the nearest STREET it belongs to. I show a screen shot with the expected result:

enter image description here

Features geometries are as follows:

  • HOUSENUM → point
  • PARCEL → irregular polygon
  • STREET → polyline
2
  • 1
    Visually transferring? or do you want to change the points' coordinates? Mar 22 at 9:01
  • I am thinking of a solution based on Visually transferring using Geometry generator in the label Placement options
    – Acperience
    Mar 22 at 9:54

1 Answer 1

7

This expression breaks the boundary of the parcel polygons into line segments, then finds the segment whose centroid is closest to the corresponding street in the address of the point. The centroid of that segment is then returned as the location to place the label.

The expression to apply on the point layer:

QGIS >=3.28
with_variable('street',
    get_feature('streets', 'name', "street"),   -- where 'streets' is the name of the streets layer and 'name' is the attribute field containing the name/code of the street
    with_variable('bnd',
        boundary(           -- get the boundary of the parcel as a line
            geometry(
                get_feature('parcels', 'parcel', "parcel") -- where 'parcels' is the name of the parcel layer and 'parcel' is the attiribute field containing the name/code of the parcel
            )
        ),
        with_variable('segments', geometries_to_array(segments_to_lines(@bnd)),       -- break boundary into segments and convert multiline to an array of individual geometries
            with_variable('dists',
                array_foreach(        -- loop over the array of geometries and get the distance from the centroid to the corresponding street
                    @segments,
                    distance(geometry(@street), centroid(@element))
                ),
                -- get the position in the array of the shortest distance and then use that position to find the geometry in the @segments array
                centroid( array_get(@segments, array_find(@dists, array_min(@dists))) )
            )
        )
    )
)

In the image, the blue points are the originals, and the red points signify the location of the label. I used points to make things clearer visually, but the same expression can be used in the Geometry Generator of the label placement options.

enter image description here

An adaptation to the expression to move the point a set distance inside the parcel:

with_variable('street',
    get_feature('streets', 'name', "street"),
    with_variable('bnd',
        boundary(
            geometry(
                get_feature('parcels', 'parcel', "parcel")
            )
        ),
        with_variable('segments', geometries_to_array(segments_to_lines(@bnd)),
            with_variable('dists',
                array_foreach(
                    @segments,
                    distance(geometry(@street), centroid(@element))
                ),
                -- make a line between the parcel centroid and the centroid of the segment
                with_variable('line',
                    make_line(centroid(@bnd), centroid( array_get(@segments, array_find(@dists, array_min(@dists))) )),

                    -- cut this line short by a specified distance (5m) and get its endpoint (the new label position)
                    end_point(line_substring(@line, 0, length(@line) - 5))
                )
            )
        )
    )
)

enter image description here

Without the use of geometries_to_array function (QGIS <3.28)

The second expression rewritten for versions of QGIS prior to the geometries_to_array function being added:

with_variable('street',
    get_feature('streets', 'name', "street"),
    with_variable('bnd',
        boundary(
            geometry(
                get_feature('parcels', 'parcel', "parcel")
            )
        ),
        
        with_variable('segments', 
            array_foreach(
                generate_series(1, num_geometries(segments_to_lines(@bnd))), 
                geometry_n(segments_to_lines(@bnd), @element)
            ),
            with_variable('dists', 
                array_foreach(        -- loop over the array of geometries and get the distance from the centroid to the corresponding street
                    @segments,
                    distance(geometry(@street), centroid(@element))
                ),
                -- make a line between the parcel centroid and the centroid of the segment
                with_variable('line',
                    make_line(centroid(@bnd), centroid( array_get(@segments, array_find(@dists, array_min(@dists))) )),

                    -- cut this line short by a specified distance (5m) and get its endpoint (the new label position)
                    end_point(line_substring(@line, 0, length(@line) - 5))
                )
            )
        )
    )
)
4
  • Sorry. In the tests I am doing to implement this expression I get the error "Function is not known syntax error, unexpected ')', expecting $end'". I think I'm not interpreting the layer, attibute and value parameters of the 'get_feature' function: get_feature('STREETS','ID_STREET', "A"). I'm not sure about the 'value' to match. Do I have to put a unique street name? Or maybe it seems that my version of QGIS (3.26.3) does not contain in the core functions the function 'geometries_to_array'?
    – Acperience
    Mar 22 at 15:15
  • 1
    No need to apologise :). The street name (or ID in your case) should be picked up automatically from the point layer, if it exists there. Instead of "A" you can use "ID_STREET" (the name of the field in the point layer containing the street).
    – Matt
    Mar 22 at 15:37
  • Solution accepted. The 'geometries_to_array' function is only available as of development version 3.28
    – Acperience
    Mar 22 at 18:51
  • 1
    See my third expression. I re-wrote the second one without using geometries_to_array. I hope this does what you need.
    – Matt
    Mar 22 at 19:18

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.