source: subversion/applications/utils/cadastre-france/svg-parser.pl @ 26424

Last change on this file since 26424 was 22846, checked in by vvass, 9 years ago

handling multipolygon relations for non-filled ways

File size: 14.6 KB
Line 
1#!/usr/bin/perl
2# WTFPLv2 [http://sam.zoy.org/wtfpl/]
3use strict;
4use XML::Parser;
5use Geo::OSR;
6use Getopt::Std;
7
8our ($opt_p,$opt_b,$opt_r,$opt_w,$opt_l,$opt_t);
9getopts("l:p:b:r:w:t:");
10unless ($opt_p || $opt_b || $opt_r || $opt_w || $opt_l || $opt_t) {
11# Options par defaut, on affiche les limites administratives, le bati,
12# les waterway et les riverbank sur la sortie standard
13    $opt_b = $opt_r = $opt_w = $opt_t = "-";
14}
15
16# Associe chaque nom de fichiers avec son filehandler.
17my %files;
18
19sub handle_opts {
20    my ($str) = @_;
21    if (defined($str) && !defined($files{$str})) {
22        my $filehandle;
23        open($filehandle,">$str") or die "Impossible d'ouvrir : $!";
24        $files{$str} = \$filehandle;
25    }
26    return $files{$str};
27}
28
29# Imprime une chaîne de caractères sur tous les fichiers ouverts
30sub print_files {
31    my ($str) = @_;
32    if (%files) {
33        for my $file (keys %files) {
34            print {${$files{$file}}} $str;
35        }
36    }
37    else {
38        print STDOUT $str;
39    }
40}
41
42handle_opts($opt_l);
43handle_opts($opt_p);
44handle_opts($opt_b);
45handle_opts($opt_r);
46handle_opts($opt_w);
47handle_opts($opt_t);
48
49if (($#ARGV % 5) != 0) {
50    print "Usage: svg-parser.pl (-lpbrwt) [IGNF] [[fichier.svg] [bbox] ..]\n";
51    exit;
52}
53
54my ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = localtime(time);
55my $tag_source = "cadastre-dgi-fr source : Direction Générale des Impôts - Cadastre. Mise à jour : " . ($year + 1900);
56my $tag_version = "v0.3";
57
58# Identifie à quoi correspond chaque couleur de remplissage du svg
59my %couleurs_remplissage = ("ffffff" => "bbox",
60                            # rgb(100%,89.802551%,59.999084%) ffe599
61                            "ffe599" => "building_nowall",
62                            # rgb(100%,79.998779%,19.999695%) ffcc33
63                            "ffcc33" => "building",
64                            # fill:rgb(59.606934%,76.470947%,85.488892%) 98c3da
65                            "98c3da" => "water",
66                            # fill:rgb(10.195923%,47.842407%,67.449951%) 1a7aac
67                            "1a7aac" => "riverbank"
68    );
69
70# Identifie à quoi correspond chaque objet qui ne peut être identifié
71# uniquement grâce à sa couleur de remplissage
72# Pour les ways fermés
73my %style_closed = (
74    "fill:none;stroke-width:0.77;stroke-linecap:butt;stroke-linejoin:miter;stroke:rgb(0%,0%,0%);stroke-opacity:1;stroke-miterlimit:10;" => "parcelle",
75    "fill:none;stroke-width:18;stroke-linecap:butt;stroke-linejoin:miter;stroke:rgb(100%,100%,100%);stroke-opacity:1;stroke-miterlimit:10;" => "limite"
76    );
77
78# Pour les ways ouverts
79my %style_opened = (
80    "fill:none;stroke-width:0.77;stroke-linecap:butt;stroke-linejoin:miter;stroke:rgb(0%,0%,0%);stroke-opacity:1;stroke-dasharray:17.72,11.82;stroke-miterlimit:10;" => "trottoir",
81    "fill:none;stroke-width:0.77;stroke-linecap:butt;stroke-linejoin:miter;stroke:rgb(0%,0%,0%);stroke-opacity:1;stroke-dasharray:5.9,5.9;stroke-miterlimit:10;" => "trottoir",
82    "fill:none;stroke-width:3.55;stroke-linecap:butt;stroke-linejoin:miter;stroke:rgb(0%,0%,0%);stroke-opacity:1;stroke-miterlimit:10;" => "train",
83    "fill:none;stroke-width:0.77;stroke-linecap:butt;stroke-linejoin:miter;stroke:rgb(0%,0%,0%);stroke-opacity:1;stroke-dasharray:5.9,11.82;stroke-miterlimit:10;" => "footpath"
84    );
85
86my %tags = ("building" =>        " <tag k=\"building\" v=\"yes\"/>\n",
87            "building_nowall" => " <tag k=\"building\" v=\"yes\"/>\n"
88                               . " <tag k=\"wall\" v=\"no\"/>\n",
89            # La couleur ne permet pas de différencier une piscine, d'une mare, d'une fontaine, d'un lac
90            "water" =>           " <tag k=\"natural\" v=\"water\"/>\n",
91            "riverbank" =>       " <tag k=\"waterway\" v=\"riverbank\"/>\n",
92            "parcelle" =>        " <tag k=\"natural\" v=\"land\"/>\n",
93            "limite" =>          " <tag k=\"boundary\" v=\"administrative\"/>\n",
94            "trottoir" =>        " <tag k=\"man_made\" v=\"sidewalk\"/>\n",
95            "train" =>           " <tag k=\"railway\" v=\"rail\"/>\n",
96            "footpath" =>        " <tag k=\"highway\" v=\"footpath\"/>\n"
97    );
98
99our @bbox_lbr93;
100our @bbox_pts;
101# Hash qui à chaque point associe une ref
102my %points;
103my @ways;
104my @relations;
105my $refnodes = 0;
106my $refways = 0;
107my $refrel = 0;
108
109my $source = Geo::OSR::SpatialReference->create ();
110my $target = Geo::OSR::SpatialReference->create ();
111
112$source->ImportFromProj4("+init=IGNF:" . shift . " +wktext");
113$target->ImportFromEPSG ('4326');
114
115my $transf = new Geo::OSR::CoordinateTransformation ($source, $target);
116
117my $parser = new XML::Parser ( Handlers => {
118    Start => \&hdl_start,
119    End   => \&hdl_end,
120    Default => \&hdl_def
121                               });
122my $surface = 0;
123my $profondeur_groupe = 0;
124
125sub hdl_start {
126    my  ($p, $elt, %atts) = @_;
127    $profondeur_groupe++ if ($surface == 1 && $elt eq 'g');
128    $surface = 1  if ($surface == 0 && $elt eq 'g' && $atts{'id'} eq 'surface0');
129    if ($surface && $elt eq 'path' )
130    {
131        my @m  = get_matrix ($atts{'transform'});
132        if ($atts{'style'} =~ m/fill:rgb\(/)
133        {
134            my ($rouge,$vert,$bleu) = ($atts{'style'} =~ m/fill:rgb\((\d*\.?\d*)%,(\d*\.?\d*)%,(\d*\.?\d*)%\)/);
135            my $couleur_hexa = (hexa ($rouge)).(hexa ($vert)).(hexa ($bleu));
136
137            my $type = $couleurs_remplissage{$couleur_hexa};
138
139            my $s = $atts{'d'};
140            if (defined($type)) {
141                my @points = lire_points (\$s,@m);
142                if ($#bbox_pts != 3) {
143                    if ($type eq "bbox")
144                    {
145                        @bbox_pts = minmax(@points);
146                    }
147                }
148                else
149                {
150                    if (($#points >= 0) && est_dans_bbox(@points))
151                    {
152                        my $ref = new_rel("multipolygon");
153                        rel_add_way($ref,new_way($type,\@points)) if ($#points >= 0);
154                        while ($s =~ m/M (-?\d*\.?\d*) (-?\d*\.?\d*) L/)
155                        {
156                            my @points = lire_points (\$s,@m);
157# Le signe de la référence au way indique le sens d'orientation du polygone
158                            rel_add_way($ref,(-1+2*(aire_polygone(@points)>0)) * new_way($type,\@points)) if ($#points >= 0);
159                        }
160                    }
161                }
162            }
163        }
164        elsif ($#bbox_pts == 3)
165        {
166            $atts{'style'} =~ s/^ *//;
167            $atts{'style'} =~ s/ *$//;
168            my $type;
169            if ($atts{'d'} =~ m/Z/) {
170                $type = $style_closed{$atts{'style'}};
171            }
172            else {
173                $type = $style_opened{$atts{'style'}};
174            }
175            if (defined($type)) {
176                my $s = $atts{'d'};
177                my @points = lire_points (\$s,@m);
178                if (est_dans_bbox(@points)) {
179                    my $ref = new_rel("multipolygon");
180                    rel_add_way($ref,new_way($type,\@points)) if ($#points >= 0);
181                    while ($s =~ m/M (-?\d*\.?\d*) (-?\d*\.?\d*) L/)
182                    {
183                        my @points = lire_points (\$s,@m);
184                        rel_add_way($ref,new_way($type,\@points)) if ($#points >= 0);
185                    }
186                }
187            }
188        }
189    }
190}
191
192sub hdl_end {
193    my  ($p, $elt, %atts) = @_;
194    if ($surface == 1 && $elt eq 'g') {
195        if ($profondeur_groupe == 0) {
196            $surface = 0;
197            @bbox_pts = ();
198        }
199        else {
200            $profondeur_groupe--;
201        }
202    }
203}
204
205sub hdl_def {}
206
207sub lire_points {
208    my ($s,@m) = @_;
209    my @points;
210    return unless $$s =~ s/^M //;
211    $points[0] = transform_point ($s,@m);
212    my $i = 1;
213    while ($$s =~ s/^L //)
214    {
215        $points[$i] = transform_point ($s,@m);
216        $i += 1;
217    }
218    $$s =~ s/^Z //;
219    return @points
220}
221
222# Transforme les coordonnées d'un point pris à la tête d'une chaîne
223# pour obtenir des coordonnées en Lambert93
224sub transform_point {
225    my ($s,@m) = @_;
226    my $p;
227
228    ($p->[0],$p->[1]) = $$s =~ m/(-?\d*\.?\d*) (-?\d*\.?\d*) ?/;
229    $$s =~ s/(-?\d*\.?\d*) (-?\d*\.?\d*) ?//;
230
231    # Transformations dues à la matrice associées au path
232    ($p->[0],$p->[1]) = (
233        ($m[0]*$p->[0] +  $m[2]*$p->[1] + $m[4]),
234        ($m[1]*$p->[0] +  $m[3]*$p->[1] + $m[5]));
235
236    if ($#bbox_pts == 3)
237    {
238        # On "convertit" les coordonnées à partir du référentiel du
239        # pdf (en pts) vers du LAMBERT93
240        ($p->[0],$p->[1]) = (
241            (($p->[0]
242              - $bbox_pts[0]) * ($bbox_lbr93[2]-$bbox_lbr93[0])/($bbox_pts[2]-$bbox_pts[0])
243             + $bbox_lbr93[0]),
244            (($p->[1]
245              - $bbox_pts[1]) * ($bbox_lbr93[1]-$bbox_lbr93[3])/($bbox_pts[3]-$bbox_pts[1])
246             + $bbox_lbr93[3])
247            );
248    }
249    # Si la bbox n'a pas encore été définie, on retourne les
250    # coordonnées brutes, dans le référentiel du pdf
251    return $p;
252}
253
254sub get_matrix {
255    my ($s) = @_;
256    return split (/,/, $s) if $s =~ s/matrix\((.*)\)/$1/;
257    return (1,0,0,1,0,0)
258}
259
260sub minmax {
261    my @nodes = @_;
262    my ($xmin,$ymin,$xmax,$ymax);
263    my $node;
264    foreach $node (@nodes) {
265        $xmin = $node->[0] if (!defined($xmin) || $node->[0] < $xmin);
266        $ymin = $node->[1] if (!defined($ymin) || $node->[1] < $ymin);
267        $xmax = $node->[0] if (!defined($xmax) || $node->[0] > $xmax);
268        $ymax = $node->[1] if (!defined($ymax) || $node->[1] > $ymax);
269    }
270    return ($xmin,$ymin,$xmax,$ymax)
271}
272
273# Teste si le premier node d'un way se trouve dans la bbox, si il est
274# sur un bord, teste le point suivant, si tous les points sont sur des
275# bords retourne vrai
276sub est_dans_bbox {
277    my @nodes = @_;
278    for my $node (@nodes) {
279        if ($node->[0] >= $bbox_lbr93[0] &&
280            $node->[1] >= $bbox_lbr93[1] &&
281            $node->[0] <= $bbox_lbr93[2] &&
282            $node->[1] <= $bbox_lbr93[3])
283        {
284            if (!($node->[0] == $bbox_lbr93[0] ||
285                  $node->[1] == $bbox_lbr93[1] ||
286                  $node->[0] == $bbox_lbr93[2] ||
287                  $node->[1] == $bbox_lbr93[3]))
288            {
289                return 1;
290            }
291        }
292        else
293        {
294            return 0;
295        }
296    }
297    return 1;
298}
299
300# Retourne l'aire d'un polygone * 2 (peut servir à déterminer le sens)
301sub aire_polygone
302{
303    my (@points) = @_;
304    my $somme;
305
306    for my $i (0..$#points) {
307        $somme += $points[$i-1][0]*$points[$i][1] - $points[$i][0]*$points[$i-1][1];
308    }
309    return $somme;
310}
311
312# Transforme un pourcentage en sa valeur héxadécimale (de 00 à ff)
313sub hexa {
314    my ($pourcent) = @_;
315    # on arrondi au plus proche car sinon sprintf prend la valeur tronquée
316    return sprintf("%02x", int ($pourcent * 255 / 100 + 0.5));
317}
318
319sub new_point {
320    my ($lat,$lon,$type) = @_;
321    my $str = sprintf("%.2f,%.2f",$lon,$lat);
322    if (defined($points{$str}))
323    {
324        push @{$points{$str}{"type"}}, $type;
325        return $points{$str}{"ref"};
326    }
327    else
328    {
329        $points{$str}{"coord"} = [$lon,$lat];
330        $points{$str}{"ref"} = $refnodes;
331        $points{$str}{"type"} = [$type];
332        return $refnodes++;
333    }
334}
335
336sub new_way {
337    my ($type,$points) = @_;
338    my %way;
339    $way{"type"} = $type;
340    $way{"ref"} = $refways;
341    foreach my $node (@$points) {
342        my ($lon,$lat) = ($node->[0],$node->[1]);
343        push @{$way{"nodes"}}, new_point($lat,$lon,$type);
344    }
345    $ways[$refways] = \%way;
346    return $refways++;
347}
348
349sub new_rel {
350    my ($type) = @_;
351    my %rel;
352    $rel{"type"} = $type;
353    $rel{"ref"} = $refrel;
354    $relations[$refrel] = \%rel;
355    return $refrel++;
356}
357
358sub rel_add_way {
359    my ($ref,@ways) = @_;
360    push @{$relations[$ref]{"ways"}},@ways;
361}
362
363my @points_imprimes = ();
364my @ways_imprimes = ();
365sub print_point
366{
367    my ($lon,$lat,$ref,$file) = @_;
368    if (defined($file) && !defined($points_imprimes[$ref]{$file}))
369    {
370        ($lon,$lat) = @{$transf->TransformPoint ($lon,$lat)};
371        print {${$files{$file}}} "<node id=\"" , -1-$ref , "\" lat=\"$lat\" lon=\"$lon\"/>\n";
372        $points_imprimes[$ref]{$file} = 1;
373    }
374}
375
376sub print_way
377{
378    my ($ref_way,$file,$type,$tag) = @_;
379    if (defined($file) && !defined($ways_imprimes[$ref_way]{$file})) {
380        print {${$files{$file}}} "<way id=\"" , -1-$ref_way , "\">\n";
381        foreach my $ref_node (@{$ways[$ref_way]{"nodes"}})
382        {
383            print {${$files{$file}}} " <nd ref=\"" , -1-$ref_node , "\"/>\n";
384        }
385        print {${$files{$file}}} $tags{$type} if $tag;
386        print {${$files{$file}}} " <tag k=\"source\" v=\"$tag_source\"/>\n";
387        print {${$files{$file}}} " <tag k=\"note:import-bati\" v=\"$tag_version\"/>\n";
388        print {${$files{$file}}} "</way>\n";
389    }
390}
391
392# Traite chaque fichier associé à sa bbox dans l'ordre des arguments
393my @bbox_lbr93_glob;
394while (@ARGV) {
395    my $fichier = shift;
396    $bbox_lbr93[0] = shift;
397    $bbox_lbr93[1] = shift;
398    $bbox_lbr93[2] = shift;
399    $bbox_lbr93[3] = shift;
400
401# Calcule la bbox globale
402    $bbox_lbr93_glob[0] = $bbox_lbr93[0]
403        if (!defined($bbox_lbr93_glob[0]) || $bbox_lbr93[0] < $bbox_lbr93_glob[0]);
404    $bbox_lbr93_glob[1] = $bbox_lbr93[1]
405        if (!defined($bbox_lbr93_glob[1]) || $bbox_lbr93[1] < $bbox_lbr93_glob[1]);
406    $bbox_lbr93_glob[2] = $bbox_lbr93[2]
407        if (!defined($bbox_lbr93_glob[2]) || $bbox_lbr93[2] > $bbox_lbr93_glob[2]);
408    $bbox_lbr93_glob[3] = $bbox_lbr93[3]
409        if (!defined($bbox_lbr93_glob[3]) || $bbox_lbr93[3] > $bbox_lbr93_glob[3]);
410
411    $parser->parsefile($fichier);
412}
413
414my @points_imprimes = ();
415my @ways_imprimes = ();
416# On imprime l'en-tête sur tous les fichiers de sortie
417my @bbox_wgs84_glob;
418@bbox_wgs84_glob[0,1] = @{$transf->TransformPoint (@bbox_lbr93_glob[0,1])};
419@bbox_wgs84_glob[2,3] = @{$transf->TransformPoint (@bbox_lbr93_glob[2,3])};
420print_files "<?xml version='1.0' encoding='UTF-8'?>\n";
421print_files "<osm version='0.6' generator='plop'>\n";
422print_files "<bounds minlat=\"$bbox_wgs84_glob[1]\" minlon=\"$bbox_wgs84_glob[0]\" maxlat=\"$bbox_wgs84_glob[3]\" maxlon=\"$bbox_wgs84_glob[2]\"/>\n";
423
424for my $coord_point (sort keys %points) {
425    my ($lon,$lat) = @{$points{$coord_point}{"coord"}};
426    my $ref = $points{$coord_point}{"ref"};
427    for my $type (@{$points{$coord_point}{"type"}}) {
428        print_point($lon,$lat,$ref,$opt_b) if (($type eq "building_nowall" || $type eq "building"));
429        print_point($lon,$lat,$ref,$opt_w) if (($type eq "water"));
430        print_point($lon,$lat,$ref,$opt_r) if (($type eq "riverbank"));
431        print_point($lon,$lat,$ref,$opt_p) if (($type eq "parcelle" || $type eq "trottoir" || $type eq "footpath"));
432        print_point($lon,$lat,$ref,$opt_t) if (($type eq "train"));
433        print_point($lon,$lat,$ref,$opt_l) if (($type eq "limite"));
434    }
435}
436
437for my $rel (@relations) {
438    if ($#{$rel->{"ways"}} >= 0) {
439        my $tag = 1;
440        my $file;
441        my $first_way = $ways[$rel->{"ways"}[0]];
442        my $type = $first_way->{"type"};
443
444        $file = $opt_b if (($type eq "building_nowall" || $type eq "building"));
445        $file = $opt_w if (($type eq "water"));
446        $file = $opt_r if (($type eq "riverbank"));
447        $file = $opt_p if (($type eq "parcelle" || $type eq "trottoir" || $type eq "footpath"));
448        $file = $opt_t if (($type eq "train"));
449        $file = $opt_l if (($type eq "limite"));
450
451        next unless (defined($file));
452
453        for my $ref_way (@{$rel->{"ways"}}) {
454            my $type = $ways[$ref_way]{"type"};
455            # N'écrit pas le tag pour un inner
456            print_way(abs($ref_way),$file,$type,($ref_way>=0));
457        }
458        if ($#{$rel->{"ways"}} >= 1) {
459            print {${$files{$file}}} "<relation id=\"" , -1-$rel->{"ref"} , "\">\n";
460            print {${$files{$file}}} " <tag k=\"type\" v=\"" , $rel->{"type"} , "\"/>\n";
461            print {${$files{$file}}} " <member type=\"way\" ref=\"" , -1-$rel->{"ways"}[0] , "\" role=\"outer\"/>\n";
462            my $ways = $rel->{"ways"};
463            foreach my $way (@{$ways}[1..$#{$ways}])
464            {
465                # L'orientation du polygone indique si on ajoute ou si on enlève
466                my ($role,$ref);
467                if ($way > 0) {
468                    $role = "outer";
469                    $ref = $way;
470                }
471                else {
472                    $role = "inner";
473                    $ref = -$way;
474                }
475                print {${$files{$file}}} " <member type=\"way\" ref=\"" , -1-$ref , "\" role=\"$role\"/>\n";
476            }
477            print {${$files{$file}}} "</relation>\n";
478        }
479    }
480}
481
482# On ferme les balises ouvertes
483print_files "</osm>";
Note: See TracBrowser for help on using the repository browser.