lucidiot's cybrecluster

Extended Well-Known Text

Lucidiot Informatique 2022-07-05
Je croyais en avoir fini avec mes expressions régulières, mais ce serait trop facile.


Ce n'est que quand j'ai travaillé sur le Well-Known Binary que j'ai vraiment pu comprendre les différentes variantes du WKB et du WKT et leur importance. Mais avant de travailler sur le WKB, j'avais déjà créé une très longue expression régulière pour valider un fichier WKT, en me basant exclusivement sur la spécification SFA de l'OGC, au doux nom de OGC 06-103r4. Je n'avais ni tenu compte des nombreuses définitions supplémentaires du standard ISO 13249-3, ni des ajouts non officiels de PostGIS dans son Extended Well-Known Text. Je vais maintenant remédier à cette terrible erreur, qui sans nul doute a dû choquer bon nombre d'entre vous tant le sujet est important et facile à comprendre.

Avant de s'attaquer au monstre qu'est ISO 13249-3, on va donc commencer par l'Extended Well-Known Text. La documentation officielle de PostGIS contient déjà plus d'informations que pour l'EWKB, y compris des exemples. On y découvre ainsi deux modifications majeures par rapport à l'état dans lequel on avait laissé tout ce bazar :

Une erreur de lecture

Vous remarquerez que dans la liste à puces précédente, j'ai utilisé POINTM, et non POINT M pour indiquer la présence d'une mesure. La grammaire définissant le WKT dans les spécifications OGC et ISO est sous notation BNF, où chaque partie du texte est séparée par des espaces, ce qui m'a induit en erreur. L'espace n'est pas obligatoire entre le nom d'une géométrie et son indicateur de dimensionnalité, mais il reste obligatoire pour indiquer une géométrie vide : POINTM EMPTY est permis, mais pas POINTMEMPTY. On va commencer par résoudre ce problème avant de rajouter nos nouvelles fonctionnalités.

Pour rappel, voici à quoi ressemble pour le moment notre script :

define(ignorecase, `patsubst($1, \w, `[`\&'translit(\&, A-Z, a-z)]')')dnl
define(number, [+-]?(?:\d+(?:[.,]\d*)?|[.,]\d+)(?:ignorecase(E)[+-]?\d+)?)dnl
define(coords_2, number\s+number)dnl
define(coords_3, number\s+number\s+number)dnl
define(coords_4, number\s+number\s+number\s+number)dnl
define(emptyable, `(?:'ignorecase(EMPTY)`|\(\s*$1\s*\))')dnl
define(make_list, emptyable($1(?:\s*,\s*$1)*))dnl
define(make_geometry, `define($1, `ignorecase($2)\s*(?:\s+ignorecase(EMPTY)|\(\s*$3\s*\))')$1')dnl
define(make_geometry_list, `make_geometry($1, $2, $3(?:\s*,\s*$3)*)')dnl
define(make_geometries,
`make_geometry(point_$2, POINT$1, coords_$2)
make_geometry_list(linestring_$2, LINESTRING$1, coords_$2)
make_geometry_list(polygon_$2, (?:POLYGON|TRIANGLE|MULTILINESTRING)$1, make_list(coords_$2))
make_geometry_list(multipoint_$2, MULTIPOINT$1, emptyable(coords_$2))
make_geometry_list(multipolygon_$2, (?:MULTIPOLYGON|POLYHEDRALSURFACE|TIN)$1, make_list(make_list(coords_$2)))
make_geometry_list(geometrycollection_$2, GEOMETRYCOLLECTION$1, (?:point_$2|linestring_$2|polygon_$2|multipoint_$2|multipolygon_$2))')dnl
make_geometries(`', 2)
make_geometries(` (?:Z|M)', 3)
make_geometries(` ZM', 4)

Les espaces en trop se trouvent au niveau des deux derniers appels à make_geometries. On remarque d'ailleurs que je n'avais même pas permis d'autres types d'espaces ou plusieurs espaces, comme par exemple un POINT M. Tout ce que nous devrions donc avoir à faire, c'est remplacer ces espaces uniques par une expression régulière, pour indiquer 0 ou une infinité d'espaces :

make_geometries(`', 2)
make_geometries(\s*(?:Z|M), 3)
make_geometries(\s*ZM, 4)

J'ai également retiré les apostrophes, puisqu'elles ne sont plus utiles. Elles ne servaient qu'à indiquer qu'un espace n'était pas seulement là pour de la présentation et ne devait pas être ignoré dans les arguments de macro.

En testant cette modification, on s'aperçoit cependant qu'aucune géométrie à 3 ou 4 dimensions ne fonctionne à présent ! Si on inspecte longuement les hiéroglyphes qui composent l'expression entière, on observe que les \s* ont été en fait transformés eux aussi par ignorecase ; on a dans l'expression finale un \[ss]*, ce qui n'a aucun sens.

Dans un langage normal, j'irais modifier la macro ignorecase pour qu'elle ne fasse pas attention aux lettres précédées d'une controblique. Mais avec m4, on a droit au moteur d'expressions régulières de base de Emacs, qui est loin d'avoir les fonctionnalités dont on a besoin pour faire ce genre de vérification. Une alternative donc est d'ajouter un nouvel argument à make_geometry pour qu'elle accepte un suffixe qui est ajouté après l'appel à ignorecase, ce qui nous permet de mettre tout ce qu'on veut sans être bloqués. On s'occupera d'ignorer la casse comme il faut dans notre suffixe nous-mêmes.

define(make_geometry, `define($1, `ignorecase($2)$3\s*(?:\s+ignorecase(EMPTY)|\(\s*$4\s*\))')$1')dnl
define(make_geometry_list, `make_geometry($1, $2, $3, $4(?:\s*,\s*$4)*)')dnl
define(make_geometries,
`make_geometry(point_$2, POINT, $1, coords_$2)
make_geometry_list(linestring_$2, LINESTRING, $1, coords_$2)
make_geometry_list(polygon_$2, (?:POLYGON|TRIANGLE|MULTILINESTRING), $1, make_list(coords_$2))
make_geometry_list(multipoint_$2, MULTIPOINT, $1, emptyable(coords_$2))
make_geometry_list(multipolygon_$2, (?:MULTIPOLYGON|POLYHEDRALSURFACE|TIN), $1, make_list(make_list(coords_$2)))
make_geometry_list(geometrycollection_$2, GEOMETRYCOLLECTION, $1, (?:point_$2|linestring_$2|polygon_$2|multipoint_$2|multipolygon_$2))')dnl
make_geometries(`', 2)
make_geometries(\s*(?:ignorecase(Z|M)), 3)
make_geometries(\s*ignorecase(ZM), 4)

On voit maintenant que make_geometry attend 4 arguments : un nom de macro, un nom de géométrie en majuscules, un suffixe, et un contenu quand la géométrie n'est pas vide. Le changement se répercute sur make_geometry_list, qui prend les mêmes arguments dans le même ordre. On modifie ensuite les arguments qu'on a envoyé à make_geometries pour ignorer la casse sur Z et M uniquement.

Maintenant que ce premier obstacle, qui concerne donc le Well-Known Text original et pas seulement l'Extended Well-Known Text, est derrière nous, on va pouvoir s'attaquer à l'EWKT pour de vrai.

Ajout du SRID

La partie la plus simple de l'EWKT est d'ajouter le SRID. On va utiliser un groupe non-capturant optionnel (?:...)? pour ajouter une sous-expression qui doit soit être présente en totalité, soit pas du tout. On souhaite traiter SRID=1234;, où 1234 est un nombre entier totalement arbitraire, avec optionellement un signe (+1 et -1 sont autorisés). On peut donc exprimer ce nombre avec un simple [+-]?\d+. On obtient donc (?:SRID=[+-]\d+;)?.

Puisque tout le format WKT est insensible à la casse, on ajoutera un appel de notre macro m4 : (?:ignorecase(SRID)=[+-]\d+;)?. On pourrait juste écrire (?:[Ss][Rr][Ii][Dd]=[+-]\d+;)? directement, c'est suffisamment court pour encore être relativement lisible, mais on comprend mieux l'intention avec une macro.

On va devoir ajouter ce groupe non-capturant devant chacune des géométries, donc ça semble assez logique de l'ajouter à make_geometry. Cependant, make_geometry définit une macro pour chaque géométrie, la permettant d'être réutilisée dans la GEOMETRYCOLLECTION qui contient toutes les géométries avec leurs noms complets. Notre préfixe de SRID ne doit être présent qu'une seule fois, au tout début de la chaîne de caractères, en toutes circonstances, et ne peut pas être répété plus d'une fois dans de l'EWKT. On va donc plutôt préfixer l'intégralité de notre expression régulière avec notre nouveau groupe.

Avant de pouvoir ajouter ce préfixe, modifions le script pour qu'il ne génère qu'une seule expression régulière à la fois. Pour l'instant, il crée 18 expressions, sur 18 lignes différentes. On va rajouter des dnl pour supprimer les sauts de ligne, puis combiner toutes les expressions en un seul groupe non-capturant en utilisant des alternatives ((?:...|...|...)) :

define(make_geometries,
`make_geometry(point_$2, POINT, $1, coords_$2)|dnl
make_geometry_list(linestring_$2, LINESTRING, $1, coords_$2)|dnl
make_geometry_list(polygon_$2, (?:POLYGON|TRIANGLE|MULTILINESTRING), $1, make_list(coords_$2))|dnl
make_geometry_list(multipoint_$2, MULTIPOINT, $1, emptyable(coords_$2))|dnl
make_geometry_list(multipolygon_$2, (?:MULTIPOLYGON|POLYHEDRALSURFACE|TIN), $1, make_list(make_list(coords_$2)))|dnl
make_geometry_list(geometrycollection_$2, GEOMETRYCOLLECTION, $1, (?:point_$2|linestring_$2|polygon_$2|multipoint_$2|multipolygon_$2))')dnl
(?:make_geometries(`', 2)|dnl
make_geometries(\s*(?:ignorecase(Z|M)), 3)|dnl
make_geometries(\s*ignorecase(ZM), 4))

Nous avons maintenant un script qui génère une seule ligne de presque 34000 caractères, comprenant un groupe non-capturant avec 18 alternatives. On peut maintenant préfixer tout ce groupe avec notre SRID optionnel :

(?:ignorecase(SRID)=[+-]?\d+;)?dnl
(?:make_geometries(`', 2)|dnl
make_geometries(\s*(?:ignorecase(Z|M)), 3)|dnl
make_geometries(\s*ignorecase(ZM), 4))

Indicateur de dimensionnalité optionnel

Je n'avais pas encore utilisé le terme indicateur de dimensionnalité dans les articles précédents, j'utilisais plus tôt suffixe, mais je l'aime bien. Si ça continue, on finira par avoir des matrices inertielles de géométries quantiques, qui sait ?

Les règles de cet indicateur sont maintenant différentes d'avant. On avait une seule façon valide d'écrire les géométries dans chaque dimension, mais maintenant on a plusieurs représentations pour les géométries à trois et quatre dimensions : avec trois dimensions, on peut utiliser POINT, POINTZ ou POINTM, et avec quatre, on peut utiliser tout ce qu'on veut ; POINT, POINTZ, POINTM et POINTZM sont tous les quatre parfaitement valides et ont la même signification.

Fort heureusement, ces modifications sont assez simples à répercuter sur nos appels de macros :

(?:make_geometries(`', 2)|dnl
make_geometries(\s*(?:ignorecase(Z|M))?, 3)|dnl
make_geometries(\s*ignorecase(Z?M?), 4))

Dans le cas de trois dimensions, on rajoute un ? après le groupe non-capturant pour permettre soit Z, soit M, soit rien, mais pas ZM. Dans le cas de quatre dimensions, on rajoute un ? après chaque lettre pour que Z et M soient chacun optionnels indépendamment de l'autre, donc on peut avoir Z, M, rien, ou ZM, mais pas MZ.

Sortons de la documentation

S'il y a bien une chose que mon expérience professionnelle a pu m'apprendre, c'est qu'il ne faut jamais faire confiance à la documentation. Seul le code, ce qui est véritablement exécuté sur la machine, dit la vérité. Peu importe à quel point une documentation peut sembler complète, elle ne le sera jamais. Dans le cas présent, je me doutais après mon expérience avec l'EWKB que la documentation de l'EWKT, bien qu'un petit peu plus fournie, ne serait pas suffisante. Je suis donc allé consulter directement le code source de PostGIS.

Comme la plupart des extensions à PostgreSQL, PostGIS est écrit en langage C. Dans cet écosystème, une des façons les plus courantes d'interpréter du texte structuré est d'utiliser Lex et Yacc. Ces outils, introduits dans les années 1970, permettent de faire de la métaprogrammation : du code écrit en Lex ou en Yacc deviendra du code C.

Lex permet de générer un analyseur lexical, ou lexer : il découpe un texte en jetons. Par exemple, on pourrait dire que dans POINT (1 2), on a cinq jetons : POINT, (, 1, 2 et ). Lex ne s'occupera pas vraiment d'interpréter si ces jetons dans cet ordre font sens, mais validera leur contenu : par exemple, POING n'est pas un jeton reconnu, donc Lex échouera, et POINT (A B) ne serait pas non plus valide, mais un POINT (1) pourrait très bien fonctionner.

Yacc est l'analyseur syntaxique, ou parser : il reçoit les jetons trouvés par Lex et les interprète, causant cette fois des erreurs pour des constructions qui ne font pas de sens. En général, on s'en sert pour construire un arbre syntaxique dans le cadre d'un compilateur ; beaucoup de compilateurs lisent le code source avec Lex et Yacc. Dans le cas de PostGIS, Yacc est utilisé pour appeler directement les fonctions qui construisent des objets géométriques en mémoire.

Je suis donc allé essayer de lire le code source du fichier Lex et du fichier Yacc utilisés pour la lecture de l'Extended Well-Known Text. Je n'ai quasiment jamais codé en C, en tous cas rien de bien plus compliqué qu'un Hello World!, et je n'ai jamais utilisé ni Lex ni Yacc, mais j'essaie de me débrouiller en ignorant la syntaxe et en lisant les mots anglais et les expressions régulières.

J'ai pu découvrir deux modifications supplémentaires non documentées : les valeurs NaN et des types de géométrie supplémentaires.

NaN

Dans une de ces expressions régulières, j'ai pu découvrir une nouvelle valeur non documentée pour tous les nombres, NaN. La norme IEEE 754 définit des spéciales dans les nombres à virgule flottante, telles que l'infini positif et négatif, ainsi que NaN (not a number). Il est possible d'atteindre ces valeurs étranges en faisant des calculs inattendus, comme une division par zéro, la racine carrée d'un nombre négatif (qui serait un nombre complexe), ou encore diviser zéro par l'infini.

Avec des requêtes SQL, il est tout à possible d'utiliser l'infini positif, négatif et NaN comme coordonnées :

lucidiot=# SELECT ST_AsText(ST_PointZ('Infinity'::double precision, '-Infinity'::double precision, 'NaN'::double precision));
            st_astext
----------------------------------
 POINT Z (Infinity -Infinity NaN)
(1 row)

Cependant, si on essaie de relire ce WKT pour en refaire une géométrie, ça ne fonctionne pas :

lucidiot=# SELECT ST_GeomFromEWKT('POINT Z (Infinity -Infinity NaN)');
ERROR:  parse error - invalid geometry
HINT:  "POINT Z (In" <-- parse error at position 11 within geometry

On peut par contre utiliser NaN seul. Étrangement, le parser EWKT prend en charge NaN, mais pas les infinis :

lucidiot=# SELECT ST_AsText(ST_GeomFromEWKT('POINT Z (nan NAN NaN)')));
       st_astext
-----------------------
 POINT Z (NaN NaN NaN)
(1 row)

On n'a donc plus qu'à ajouter NAN à notre définition du nombre. Un nombre peut désormais être soit un nombre, soit pas un nombre !

define(number, (?:[+-]?(?:\d+(?:[.,]\d*)?|[.,]\d+)(?:ignorecase(E)[+-]?\d+)?|ignorecase(NAN)))dnl

J'ai enveloppé la définition existante du nombre dans un groupe non-capturant, pour pouvoir donner deux alternatives : celle du nombre, qui existe déjà, et celle de pas un nombre, représentée par ignorecase(NAN). Cette modification de 20 caractères ajoute 7803 caractères à notre expression régulière finale, soit un bond de 18% !

Types de géométrie supplémentaires

Un peu plus bas dans le lexer, j'ai trouvé des jetons correspondant à des types de géométries supplémentaires, qui sont en fait un peu documentés mais sont encore peu pris en charge par PostGIS de façon générale. Ces types correspondent à des géométries de ISO 13249-3, des géométries assez basiques pour la gestion des courbes. On obtient accès à la chaîne d'arcs de cercles, la courbe la plus simple du standard, ainsi qu'à diverses géométries pouvant regrouper ces chaînes d'arcs de cercles : les ensembles de courbes, les ensembles de surfaces, les polygones courbes et les courbes composées.

Par la lecture du fichier Yacc, qui peut être lu finalement de façon assez similaire à de la notation BNF, on va pouvoir déduire la syntaxe exacte pour chaque type et ainsi l'ajouter à notre expression régulière.

Chaîne d'arcs de cercles

Je ne vais pas revenir trop en détail sur ce que sont chacun des types de géométries qu'on va voir ; j'en ai déjà bien assez parlé quand j'ai traité le Well-Known Binary. La chaîne d'arcs de cercles est le type de courbe le plus simple à gérer pour nous ; c'est exactement comme une ligne brisée classique, mais dont les points sont reliés en utilisant des arcs de cercle au lieu de lignes droites.

On peut donc ajouter une alternative à notre LINESTRING existant :

make_geometry_list(linestring_$2, (?:LINESTRING|CIRCULARSTRING), $1, coords_$2)|dnl

Courbe composée

La courbe composée permet d'assembler plusieurs courbes ensembles pour former des courbes plus complexes. Chaque courbe doit se terminer là où la courbe suivante commence. On peut notamment coller la chaîne d'arcs de cercles à la ligne brisée, ce qui est l'intérêt principal — si on veut relier deux courbes du même type, on peut juste utiliser une seule courbe.

Puisque cette courbe composée peut se composer de plusieurs types de courbes, on doit spécifier le nom du type de géométrie, comme pour la collection de géométries :

COMPOUNDCURVE (
  CIRCULARSTRING (...),
  LINESTRING (...),
  LINESTRING (...),
  CIRCULARSTRING (...),
  EMPTY,
  (...)
)

La syntaxe autorise un EMPTY, bien qu'il ne soit pas clair si cela représente vraiment une géométrie valide étant donné que chaque courbe est censée de terminer là où la suivante commence — rien ne commence et ne se termine nulle part. On a aussi une autre syntaxe permise, où le type de géométrie n'est pas spécifié : ce sera alors interprété implicitement comme étant un LINESTRING.

Pour gérer les deux types de courbes et EMPTY, on peut juste reprendre la même construction que pour la collection de géométries :

make_geometry_list(compoundcurve_$2, COMPOUNDCURVE, $1, linestring_$2)|dnl

make_geometry_list s'occupe pour nous d'ajouter EMPTY, et linestring_$2 sont les macros qu'on a défini avec make_geometry_list(linestring_$2, ...) et qui contiennent donc déjà la ligne brisée et la chaîne d'arcs de cercles.

Pour gérer la syntaxe implicite sans nom de type, on peut rajouter une alternative pour une liste de coordonnées brute :

make_geometry_list(compoundcurve_$2, COMPOUNDCURVE, $1, (?:linestring_$2|make_list(coords_$2)))|dnl

Ensemble de courbes

L'ensemble de courbes est équivalent à la courbe composée, mais sans aucun prérequis sur là où chaque courbe se commence ou termine, et permettant également d'imbriquer une courbe composée :

MULTICURVE (
  CIRCULARSTRING (...),
  LINESTRING (...),
  COMPOUNDCURVE (...),
  CIRCULARSTRING (...),
  EMPTY,
  (...)
)

On peut presque dupliquer la macro de la courbe composée, en rajoutant juste la courbe composée comme enfant possible :

make_geometry_list(multicurve_$2, MULTICURVE, $1, (?:compoundcurve_$2|linestring_$2|make_list(coords_$2)))|dnl

Polygone courbe

Le polygone courbe est un polygone dont chaque anneau est une courbe. Sa syntaxe est exactement la même que pour l'ensemble de courbes :

CURVEPOLYGON (
  CIRCULARSTRING (...),
  LINESTRING (...),
  COMPOUNDCURVE (...),
  CIRCULARSTRING (...),
  EMPTY,
  (...)
)

On peut juste ajouter une alternative dans le nom de l'ensemble de courbes pour économiser un peu dans la taille de notre expression, exactement comme on a fait pour la chaîne d'arcs de cercles :

make_geometry_list(multicurve_$2, (?:MULTICURVE|CURVEPOLYGON), $1, (?:compoundcurve_$2|linestring_$2|make_list(coords_$2)))|dnl

Ensemble de surfaces

L'ensemble de surfaces est comme l'ensemble de polygones, mais il permet à la fois des polygones classiques et des polygones courbes, toujours avec un nom de type de géométrie. Si on omet ce type, on a implicitement un polygone :

MULTISURFACE (
  CURVEPOLYGON (...),
  POLYGON (...),
  EMPTY,
  (...)
)

La difficulté se trouve ici dans l'ajout du polygone simple. On a optimisé un peu et notre définition du polygone représente en fait à la fois un polygone, un triangle et un ensemble de lignes brisées :

make_geometry_list(polygon_$2, (?:POLYGON|TRIANGLE|MULTILINESTRING), $1, make_list(coords_$2))|dnl

Pour pouvoir ajouter correctement le polygone sans autoriser des triangles ou des ensembles de lignes brisées dans un ensemble de surfaces, on va devoir redéfinir à nouveau le polygone, comme une liste de listes de coordonnées. On avait fait exactement la même optimisation pour le polygone courbe juste au-dessus, donc on va devoir lui aussi le redéfinir.

make_geometry_list(curvepolygon_$2, CURVEPOLYGON, $1, (?:compoundcurve_$2|linestring_$2|make_list(coords_$2)))
make_geometry_list(only_polygon_$2, POLYGON, $1, make_list(coords_$2))

On appellera ça only_polygon pour éviter de redéfinir la macro existante polygon. On peut imbriquer cette nouvelle définition directement dans les alternatives de l'ensemble de surfaces :

make_geometry_list(multisurface_$2, MULTISURFACE, $1, (?:make_geometry_list(curvepolygon_$2, CURVEPOLYGON, $1, (?:compoundcurve_$2|linestring_$2|make_list(coords_$2)))|make_geometry_list(only_polygon_$2, POLYGON, $1, make_list(coords_$2))|make_list(make_list(coords_$2))))|dnl

Mise à jour de la collection de géométries

On n'oubliera pas de compléter la collection de géométries, qui autorise aussi tous ces nouveaux types de géométries, puisqu'elle est définie dans le fichier Yacc comme pouvant tout contenir. On ne gérera cependant toujours pas la possibilité d'une collection de géométries imbriquée dans une autre...

make_geometry_list(geometrycollection_$2, GEOMETRYCOLLECTION, $1, (?:point_$2|linestring_$2|polygon_$2|compoundcurve_$2|multipoint_$2|multipolygon_$2|multicurve_$2|multisurface_$2))

Script final

define(ignorecase, `patsubst($1, \w, `[`\&'translit(\&, A-Z, a-z)]')')dnl
define(number, (?:[+-]?(?:\d+(?:[.,]\d*)?|[.,]\d+)(?:ignorecase(E)[+-]?\d+)?|ignorecase(NAN)))dnl
define(coords_2, number\s+number)dnl
define(coords_3, number\s+number\s+number)dnl
define(coords_4, number\s+number\s+number\s+number)dnl
define(emptyable, `(?:'ignorecase(EMPTY)`|\(\s*$1\s*\))')dnl
define(make_list, emptyable($1(?:\s*,\s*$1)*))dnl
define(make_geometry, `define($1, `ignorecase($2)$3\s*(?:\s+ignorecase(EMPTY)|\(\s*$4\s*\))')$1')dnl
define(make_geometry_list, `make_geometry($1, $2, $3, $4(?:\s*,\s*$4)*)')dnl
define(make_geometries,
`make_geometry(point_$2, POINT, $1, coords_$2)|dnl
make_geometry_list(linestring_$2, (?:LINESTRING|CIRCULARSTRING), $1, coords_$2)|dnl
make_geometry_list(polygon_$2, (?:POLYGON|TRIANGLE|MULTILINESTRING), $1, make_list(coords_$2))|dnl
make_geometry_list(compoundcurve_$2, COMPOUNDCURVE, $1, (?:linestring_$2|make_list(coords_$2)))|dnl
make_geometry_list(multipoint_$2, MULTIPOINT, $1, emptyable(coords_$2))|dnl
make_geometry_list(multipolygon_$2, (?:MULTIPOLYGON|POLYHEDRALSURFACE|TIN), $1, make_list(make_list(coords_$2)))|dnl
make_geometry_list(multicurve_$2, (?:MULTICURVE|CURVEPOLYGON), $1, (?:compoundcurve_$2|linestring_$2|make_list(coords_$2)))|dnl
make_geometry_list(multisurface_$2, MULTISURFACE, $1, (?:make_geometry_list(curvepolygon_$2, CURVEPOLYGON, $1, (?:compoundcurve_$2|linestring_$2|make_list(coords_$2)))|make_geometry_list(only_polygon_$2, POLYGON, $1, make_list(coords_$2))|make_list(make_list(coords_$2))))|dnl
make_geometry_list(geometrycollection_$2, GEOMETRYCOLLECTION, $1, (?:point_$2|linestring_$2|polygon_$2|compoundcurve_$2|multipoint_$2|multipolygon_$2|multicurve_$2|multisurface_$2))')dnl
(?:ignorecase(SRID)=[+-]?\d+;)?dnl
(?:make_geometries(`', 2)|dnl
make_geometries(\s*(?:ignorecase(Z|M))?, 3)|dnl
make_geometries(\s*ignorecase(Z?M?), 4))

Exécuter ce script avec m4 ewkt.m4 donne une expression régulière si longue que je ne vais pas oser l'incorporer dans cet article. Sachez que vous pouvez le faire sur la quasi-totalité des distributions Linux, souvent sans rien installer, car m4 est souvent disponible par défaut.

On obtient une expression régulière de 275086 caractères, soit une expression presque 9 fois plus longue qu'avant. J'ai battu mon record, mais je ne suis pas sûr que ce soit vraiment un record à battre.

Conclusion

J'ai essayé d'intégrer l'expression régulière complète dans un script Perl pour pouvoir la tester, et il lui faut plusieurs secondes pour traiter chaque géométrie. C'est comme si une expression régulière n'était pas vraiment la meilleure idée pour traiter ce format, c'est étrange !

Notre script est un peu plus long que la dernière fois, mais il n'a pas non plus été si profondément transformé. Il est fort probable que la situation change avec la variante ISO du Well-Known Text ; exactement comme pour le WKB, le WKT va subir les conséquences d'un standard ISO...


Commentaires

Il n'y a pour l'instant aucun commentaire. Soyez le premier !