U
    ô‰VbÜ6  ã                   @   s°   d Z dZdZddlmZ ddlmZmZ ddlm	Z	m
Z
mZmZmZmZmZmZmZmZ ddlmZ e	jdkr‚dd	lZdd
lmZ ddlmZmZ dZdZdZG dd„ dƒZd	S )zCopyright 2021, 3LizzGPL version 3zinfo@3liz.orgé    )Ú	lru_cache)ÚTupleÚUnion)
ÚQgisÚQgsCoordinateReferenceSystemÚQgsCoordinateTransformÚQgsDataSourceUriÚQgsFeatureRequestÚQgsGeometryÚ
QgsProjectÚQgsProviderConnectionExceptionÚQgsSpatialIndexÚQgsVectorLayer)ÚQVariantéy  N)ÚQgsProviderRegistry)ÚLoggerÚ	profilingéd   z1 = 0Ú c                   @   s.  e Zd Zd$eeeedœdd„Zedœdd„Zddœd	d
„Zedœdd„Z	e
ee dœdd„Zeeeeef dœdd„ƒZeeedeedœdd„ƒƒZeeedeedœdd„ƒƒZeeedeedœdd„ƒƒZeeedeedœdd„ƒƒZeeeeef edœdd „ƒZeeeeeeed!œd"d#„ƒZdS )%ÚFilterByPolygonF)ÚconfigÚlayerÚeditingÚuse_st_relationshipc                 C   sR   d| _ || _|| _|| _t ¡ | _|| _d| _d| _	d| _
d| _d| _|  ¡  dS )zªConstructor for the filter by polygon.

        :param config: The filter by polygon configuration as dictionary
        :param layer: The vector layer to filter
        N)Ú
connectionr   r   r   r   ÚinstanceÚprojectr   Úprimary_keyÚfilter_modeÚspatial_relationshipÚpolygonÚgroup_fieldÚ_parse)Úselfr   r   r   r   © r%   ú;/var/qgis-server/plugins/lizmap/server/filter_by_polygon.pyÚ__init__(   s    
zFilterByPolygon.__init__)Úreturnc                 C   s
   | j dk	S )z2If the configuration is filtering the given layer.N)r   ©r$   r%   r%   r&   Úis_filteredH   s    zFilterByPolygon.is_filteredNc                 C   s²   | j  ¡ sdS | jdkrdS | j d¡}|s0dS |D ]@}| d¡| j  ¡ kr4| d¡| _| d¡| _| d¡| _ qvq4| jdkr„dS | j d¡}| j 	|d ¡| _
| d	¡| _dS )
z)Read the configuration and fill variablesNÚlayersr   r   r   r    r   Zpolygon_layer_idr"   )r   Ú	isSpatialr   ÚgetÚidr   r   r    r   ÚmapLayerr!   r"   )r$   r+   r   r   r%   r%   r&   r#   L   s$    


zFilterByPolygon._parsec                 C   sÂ   | j st d¡ dS | j s2| j  ¡ r2t d¡ dS | j  ¡  | j¡dk rft d | j| j  ¡ ¡¡ dS | j	 ¡ sŽt d | j
| j	 ¡ ¡¡ dS | j	 ¡  | j
¡dk r¾t d | j
| j	 ¡ ¡¡ dS )z& If the configuration is valid or not.z-The polygon layer for filtering is not valid.Fr   z4The field {} used for filtering does not exist in {}T)r!   r   ÚcriticalÚisValidÚfieldsÚindexOfr"   ÚformatÚnamer   r   r)   r%   r%   r&   Úis_validg   s<    

 ÿÿ
 ÿÿ ÿÿzFilterByPolygon.is_valid)Úurir(   c              
   C   sx   | j dkrht ¡  d¡}| | ¡ i ¡| _ z| j  d¡ W n, tk
rf } zt 	|¡ W 5 d}~X Y nX | j  |¡}|S )z> For a given URI, execute an SQL query and return the result. NÚpostgresz>SET application_name='QGIS Lizmap Server : Filter By Polygon';)
r   r   r   ZproviderMetadataÚcreateConnectionr7   Ú
executeSqlr   r   Úlog_exception)r$   r7   ÚsqlÚmetadataÚeÚresultsr%   r%   r&   Ú	sql_query„   s    
zFilterByPolygon.sql_query)Úgroupsr(   c           	      C   s>  | j dkr,| js"t d¡ tdfS t d¡ | j ¡ dkrPtjdkrP|  	|¡}n
|  
|¡}| ¡ rpd| _tdfS dj| j ¡  ¡ | | j ¡  ¡ r–d	nd
¡d}| j ¡ dkr,| jsÂtjdkr,t| j ¡ ƒ}| jdkrÞdnd}|  | j ¡ | j ¡ | ¡ ||¡}| jr||fS |  |¡|f}d| _|S |  |¡}||fS )zµ Get the SQL subset string for the current groups of the user.

        :param groups: List of groups belongings to the user.
        :returns: The subset SQL string to use
        r   zPLayer is editing only BUT we are not in an editing session. Return all features.r   zZLayer is editing only AND we are in an editing session. Continue to find the subset stringr8   r   NzSRID={crs};{wkt}é   é   )ÚcrsÚwktÚcontainsFT)r   r   r   ÚinfoÚALL_FEATURESr!   ÚproviderTyper   ÚQGIS_VERSION_INTÚ"_polygon_for_groups_with_sql_queryÚ!_polygon_for_groups_with_qgis_apiÚisEmptyr   ÚNO_FEATURESr4   rD   ÚpostgisSridÚasWktÚisGeographicr   r   r   Úsourcer    Ú_format_sql_st_relationshipÚ	sourceCrsÚgeometryColumnÚ_features_ids_with_sql_queryÚ_features_ids_with_qgis_api)	r$   rA   r!   Zewktr7   Úuse_st_intersectZst_relationÚresultZsubsetr%   r%   r&   Ú
subset_sql’   sJ    
ÿÿ
þû
zFilterByPolygon.subset_sql)Úmaxsizec                 C   s`   dj | jd |¡d}tƒ }| g ¡ | |¡ g }| j |¡D ]}| | 	¡ ¡ q@t
ƒ  |¡S )zF All features from the polygon layer corresponding to the user groups zÅ
array_intersect(
    array_foreach(
        string_to_array("{polygon_field}"),
        trim(@element)
    ),
    array_foreach(
        string_to_array('{groups}'),
        trim(@element)
    )
)ú,)Úpolygon_fieldrA   )r4   r"   Újoinr	   ÚsetSubsetOfAttributesÚsetFilterExpressionr!   ÚgetFeaturesÚappendÚgeometryr
   ÚcollectGeometry)r$   rA   Ú
expressionÚrequestZpolygon_geomsÚfeaturer%   r%   r&   rL   Í   s    ô

z1FilterByPolygon._polygon_for_groups_with_qgis_apic              
   C   sè   t | j ¡ ƒ}zdj| jd |¡| ¡ | ¡ | ¡ d}t	 
d |¡¡ |  ||¡}|d d }tƒ }t|tƒr~| ¡ r~|W S |dd… }| t |¡¡ |W S  tk
râ } z&t	 |¡ t	 d	¡ |  |¡ W Y ¢S d}~X Y nX dS )
z… All features from the polygon layer corresponding to the user groups for a Postgresql layer.

        Only for QGIS >= 3.10
        a]  
WITH current_groups AS (
    SELECT
        ARRAY_REMOVE(
            STRING_TO_ARRAY(
                regexp_replace(
                    '{groups}', '[^a-zA-Z0-9_-]', ',', 'g'
                ),
                ','
            ),
        '') AS user_group
)
SELECT
        1 AS id, ST_AsBinary(ST_Union("{geom}")) AS geom
FROM
        "{schema}"."{table}" AS p,
        current_groups AS c
WHERE
c.user_group && (
    ARRAY_REMOVE(
        STRING_TO_ARRAY(
            regexp_replace(
                "{polygon_field}", '[^a-zA-Z0-9_-]', ',', 'g'
            ),
            ','
        ),
    '')
)



r\   )r]   rA   ÚgeomÚschemaÚtablezHRequesting the database about polygons for the current groups with : 
{}r   é   rC   NzkThe filter_by_polygon._polygon_for_groups_with_sql_query failed when requesting PostGIS.
Using the QGIS API)r   r!   rR   r4   r"   r^   rU   ri   rj   r   rG   r@   r
   Ú
isinstancer   ÚisNullÚfromWkbÚbinasciiZ	unhexlifyÚ	Exceptionr;   r0   rL   )r$   rA   r7   r<   r?   Zwkbrh   r>   r%   r%   r&   rK   ë   s4     Ü&ÿ
ÿz2FilterByPolygon._polygon_for_groups_with_sql_query)Úpolygonsr(   c                 C   sÌ   t ƒ }| | j ¡ ¡ t| j ¡ | j ¡ | jƒ}| |¡ | 	| 
¡ ¡}g }|D ]l}| j |¡}| jdkrŠ| ¡  |¡r¼| || j ¡ qP| jdkr´| ¡  	|¡r¼| || j ¡ qPtdƒ‚qP|  | j|¡S )zY List all features using the QGIS API.

        :returns: The subset SQL string.
        rF   Ú
intersectszSpatial relationship unknown)r   ÚaddFeaturesr   ra   r   r!   rD   r   Ú	transformrr   ÚboundingBoxÚ
getFeaturer    rc   rF   rb   r   rp   Ú_format_sql_in)r$   rq   Úindexrt   Ú
candidatesÚ
unique_idsZcandidate_idrg   r%   r%   r&   rW   1  s     



z+FilterByPolygon._features_ids_with_qgis_api)Úst_intersectr(   c                 C   sj   t | j ¡ ƒ}dj| j| ¡ | ¡ |d}t d |dd… ¡¡ |  	||¡}dd„ |D ƒ}|  
| j|¡S )zw List all features using a SQL query.

        Only for QGIS >= 3.10

        :returns: The subset SQL string.
        z6SELECT {pk} FROM {schema}.{table} WHERE {st_intersect})Úpkri   rj   r{   z6Requesting the database about IDs to filter with {}...r   éZ   c                 S   s   g | ]}t |d  ƒ‘qS )r   )Ústr)Ú.0Úrowr%   r%   r&   Ú
<listcomp>h  s     z@FilterByPolygon._features_ids_with_sql_query.<locals>.<listcomp>)r   r   rR   r4   r   ri   rj   r   rG   r@   rw   )r$   r{   r7   r<   r?   rz   r%   r%   r&   rV   S  s    	üÿz,FilterByPolygon._features_ids_with_sql_query)r   Úvaluesr(   c                 C   sT   |st S g }|D ].}t|tƒr0| d |¡¡ q| t|ƒ¡ qdj|d |¡dS )zFormat the SQL IN statement.z'{}'z"{pk}" IN ( {values} )z , )r|   r‚   )rN   rl   r~   rb   r4   r^   )Úclsr   r‚   ÚcleanedÚvaluer%   r%   r&   rw   l  s    
zFilterByPolygon._format_sql_in)Úfiltered_crsÚfiltering_crsÚ
geom_fieldrq   rX   r(   c                 C   s:   dj |rdnd|| | ¡ r dnd¡| ¡ | ¡ d}|S )zzIf layer is of type PostgreSQL, use a simple ST_Intersects/ST_Contains.

        :returns: The subset SQL string.
        zc
{function}(
    ST_Transform(ST_GeomFromText('{wkt}', {from_crs}), {to_crs}),
    "{geom_field}"
)ZST_IntersectsZST_ContainsrB   rC   )Úfunctionrˆ   rE   Zfrom_crsZto_crs)r4   rP   rQ   rO   )rƒ   r†   r‡   rˆ   rq   rX   r<   r%   r%   r&   rS   {  s    
÷z+FilterByPolygon._format_sql_st_relationship)FF)Ú__name__Ú
__module__Ú__qualname__Údictr   Úboolr'   r*   r#   r6   r   r   r@   r   Útupler~   rZ   r   ÚCACHE_MAX_SIZEr
   rL   rK   rW   rV   Úclassmethodr   Úlistrw   r   rS   r%   r%   r%   r&   r   &   sH      ÿ   ÿ :D ùr   )Ú__copyright__Ú__license__Ú	__email__Ú	functoolsr   Útypingr   r   Ú	qgis.corer   r   r   r   r	   r
   r   r   r   r   Zqgis.PyQt.QtCorer   rJ   ro   r   Úlizmap.server.loggerr   r   r   rN   rH   r   r%   r%   r%   r&   Ú<module>   s   0
