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

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

better handling of multipolygon relations

File size: 14.4 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                        $refrel++;
161                    }
162                }
163            }
164        }
165        elsif ($#bbox_pts == 3)
166        {
167            $atts{'style'} =~ s/^ *//;
168            $atts{'style'} =~ s/ *$//;
169            my $style;
170            if ($atts{'d'} =~ m/Z/) {
171                $style = $style_closed{$atts{'style'}};
172            }
173            else {
174                $style = $style_opened{$atts{'style'}};
175            }
176            if (defined($style)) {
177                my $s = $atts{'d'};
178                my @points = lire_points (\$s,@m);
179                if (est_dans_bbox(@points)) {
180                    my $ref = new_rel("multipolygon");
181                    rel_add_way($ref,new_way($style,\@points)) if ($#points >= 0);
182                }
183            }
184        }
185    }
186}
187
188sub hdl_end {
189    my  ($p, $elt, %atts) = @_;
190    if ($surface == 1 && $elt eq 'g') {
191        if ($profondeur_groupe == 0) {
192            $surface = 0;
193            @bbox_pts = ();
194        }
195        else {
196            $profondeur_groupe--;
197        }
198    }
199}
200
201sub hdl_def {}
202
203sub lire_points {
204    my ($s,@m) = @_;
205    my @points;
206    return unless $$s =~ s/^M //;
207    $points[0] = transform_point ($s,@m);
208    my $i = 1;
209    while ($$s =~ s/^L //)
210    {
211        $points[$i] = transform_point ($s,@m);
212        $i += 1;
213    }
214    $$s =~ s/^Z //;
215    return @points
216}
217
218# Transforme les coordonnées d'un point pris à la tête d'une chaîne
219# pour obtenir des coordonnées en Lambert93
220sub transform_point {
221    my ($s,@m) = @_;
222    my $p;
223
224    ($p->[0],$p->[1]) = $$s =~ m/(-?\d*\.?\d*) (-?\d*\.?\d*) ?/;
225    $$s =~ s/(-?\d*\.?\d*) (-?\d*\.?\d*) ?//;
226
227    # Transformations dues à la matrice associées au path
228    ($p->[0],$p->[1]) = (
229        ($m[0]*$p->[0] +  $m[2]*$p->[1] + $m[4]),
230        ($m[1]*$p->[0] +  $m[3]*$p->[1] + $m[5]));
231
232    if ($#bbox_pts == 3)
233    {
234        # On "convertit" les coordonnées à partir du référentiel du
235        # pdf (en pts) vers du LAMBERT93
236        ($p->[0],$p->[1]) = (
237            (($p->[0]
238              - $bbox_pts[0]) * ($bbox_lbr93[2]-$bbox_lbr93[0])/($bbox_pts[2]-$bbox_pts[0])
239             + $bbox_lbr93[0]),
240            (($p->[1]
241              - $bbox_pts[1]) * ($bbox_lbr93[1]-$bbox_lbr93[3])/($bbox_pts[3]-$bbox_pts[1])
242             + $bbox_lbr93[3])
243            );
244    }
245    # Si la bbox n'a pas encore été définie, on retourne les
246    # coordonnées brutes, dans le référentiel du pdf
247    return $p;
248}
249
250sub get_matrix {
251    my ($s) = @_;
252    return split (/,/, $s) if $s =~ s/matrix\((.*)\)/$1/;
253    return (1,0,0,1,0,0)
254}
255
256sub minmax {
257    my @nodes = @_;
258    my ($xmin,$ymin,$xmax,$ymax);
259    my $node;
260    foreach $node (@nodes) {
261        $xmin = $node->[0] if (!defined($xmin) || $node->[0] < $xmin);
262        $ymin = $node->[1] if (!defined($ymin) || $node->[1] < $ymin);
263        $xmax = $node->[0] if (!defined($xmax) || $node->[0] > $xmax);
264        $ymax = $node->[1] if (!defined($ymax) || $node->[1] > $ymax);
265    }
266    return ($xmin,$ymin,$xmax,$ymax)
267}
268
269# Teste si le premier node d'un way se trouve dans la bbox, si il est
270# sur un bord, teste le point suivant, si tous les points sont sur des
271# bords retourne vrai
272sub est_dans_bbox {
273    my @nodes = @_;
274    for my $node (@nodes) {
275        if ($node->[0] >= $bbox_lbr93[0] &&
276            $node->[1] >= $bbox_lbr93[1] &&
277            $node->[0] <= $bbox_lbr93[2] &&
278            $node->[1] <= $bbox_lbr93[3])
279        {
280            if (!($node->[0] == $bbox_lbr93[0] ||
281                  $node->[1] == $bbox_lbr93[1] ||
282                  $node->[0] == $bbox_lbr93[2] ||
283                  $node->[1] == $bbox_lbr93[3]))
284            {
285                return 1;
286            }
287        }
288        else
289        {
290            return 0;
291        }
292    }
293    return 1;
294}
295
296# Retourne l'aire d'un polygone * 2 (peut servir à déterminer le sens)
297sub aire_polygone
298{
299    my (@points) = @_;
300    my $somme;
301
302    for my $i (0..$#points) {
303        $somme += $points[$i-1][0]*$points[$i][1] - $points[$i][0]*$points[$i-1][1];
304    }
305    return $somme;
306}
307
308# Transforme un pourcentage en sa valeur héxadécimale (de 00 à ff)
309sub hexa {
310    my ($pourcent) = @_;
311    # on arrondi au plus proche car sinon sprintf prend la valeur tronquée
312    return sprintf("%02x", int ($pourcent * 255 / 100 + 0.5));
313}
314
315sub new_point {
316    my ($lat,$lon,$type) = @_;
317    my $str = sprintf("%.2f,%.2f",$lon,$lat);
318    if (defined($points{$str}))
319    {
320        push @{$points{$str}{"type"}}, $type;
321        return $points{$str}{"ref"};
322    }
323    else
324    {
325        $points{$str}{"coord"} = [$lon,$lat];
326        $points{$str}{"ref"} = $refnodes;
327        $points{$str}{"type"} = [$type];
328        return $refnodes++;
329    }
330}
331
332sub new_way {
333    my ($type,$points) = @_;
334    my %way;
335    $way{"type"} = $type;
336    $way{"ref"} = $refways;
337    foreach my $node (@$points) {
338        my ($lon,$lat) = ($node->[0],$node->[1]);
339        push @{$way{"nodes"}}, new_point($lat,$lon,$type);
340    }
341    $ways[$refways] = \%way;
342    return $refways++;
343}
344
345sub new_rel {
346    my ($type) = @_;
347    my %rel;
348    $rel{"type"} = $type;
349    $rel{"ref"} = $refrel;
350    $relations[$refrel] = \%rel;
351    return $refrel++;
352}
353
354sub rel_add_way {
355    my ($ref,@ways) = @_;
356    push @{$relations[$ref]{"ways"}},@ways;
357}
358
359my @points_imprimes = ();
360my @ways_imprimes = ();
361sub print_point
362{
363    my ($lon,$lat,$ref,$file) = @_;
364    if (defined($file) && !defined($points_imprimes[$ref]{$file}))
365    {
366        ($lon,$lat) = @{$transf->TransformPoint ($lon,$lat)};
367        print {${$files{$file}}} "<node id=\"" , -1-$ref , "\" lat=\"$lat\" lon=\"$lon\"/>\n";
368        $points_imprimes[$ref]{$file} = 1;
369    }
370}
371
372sub print_way
373{
374    my ($ref_way,$file,$type,$tag) = @_;
375    if (defined($file) && !defined($ways_imprimes[$ref_way]{$file})) {
376        print {${$files{$file}}} "<way id=\"" , -1-$ref_way , "\">\n";
377        foreach my $ref_node (@{$ways[$ref_way]{"nodes"}})
378        {
379            print {${$files{$file}}} " <nd ref=\"" , -1-$ref_node , "\"/>\n";
380        }
381        print {${$files{$file}}} $tags{$type} if $tag;
382        print {${$files{$file}}} " <tag k=\"source\" v=\"$tag_source\"/>\n";
383        print {${$files{$file}}} " <tag k=\"note:import-bati\" v=\"$tag_version\"/>\n";
384        print {${$files{$file}}} "</way>\n";
385    }
386}
387
388# Traite chaque fichier associé à sa bbox dans l'ordre des arguments
389my @bbox_lbr93_glob;
390while (@ARGV) {
391    my $fichier = shift;
392    $bbox_lbr93[0] = shift;
393    $bbox_lbr93[1] = shift;
394    $bbox_lbr93[2] = shift;
395    $bbox_lbr93[3] = shift;
396
397# Calcule la bbox globale
398    $bbox_lbr93_glob[0] = $bbox_lbr93[0]
399        if (!defined($bbox_lbr93_glob[0]) || $bbox_lbr93[0] < $bbox_lbr93_glob[0]);
400    $bbox_lbr93_glob[1] = $bbox_lbr93[1]
401        if (!defined($bbox_lbr93_glob[1]) || $bbox_lbr93[1] < $bbox_lbr93_glob[1]);
402    $bbox_lbr93_glob[2] = $bbox_lbr93[2]
403        if (!defined($bbox_lbr93_glob[2]) || $bbox_lbr93[2] > $bbox_lbr93_glob[2]);
404    $bbox_lbr93_glob[3] = $bbox_lbr93[3]
405        if (!defined($bbox_lbr93_glob[3]) || $bbox_lbr93[3] > $bbox_lbr93_glob[3]);
406
407    $parser->parsefile($fichier);
408}
409
410my @points_imprimes = ();
411my @ways_imprimes = ();
412# On imprime l'en-tête sur tous les fichiers de sortie
413my @bbox_wgs84_glob;
414@bbox_wgs84_glob[0,1] = @{$transf->TransformPoint (@bbox_lbr93_glob[0,1])};
415@bbox_wgs84_glob[2,3] = @{$transf->TransformPoint (@bbox_lbr93_glob[2,3])};
416print_files "<?xml version='1.0' encoding='UTF-8'?>\n";
417print_files "<osm version='0.6' generator='plop'>\n";
418print_files "<bounds minlat=\"$bbox_wgs84_glob[1]\" minlon=\"$bbox_wgs84_glob[0]\" maxlat=\"$bbox_wgs84_glob[3]\" maxlon=\"$bbox_wgs84_glob[2]\"/>\n";
419
420for my $coord_point (sort keys %points) {
421    my ($lon,$lat) = @{$points{$coord_point}{"coord"}};
422    my $ref = $points{$coord_point}{"ref"};
423    for my $type (@{$points{$coord_point}{"type"}}) {
424        print_point($lon,$lat,$ref,$opt_b) if (($type eq "building_nowall" || $type eq "building"));
425        print_point($lon,$lat,$ref,$opt_w) if (($type eq "water"));
426        print_point($lon,$lat,$ref,$opt_r) if (($type eq "riverbank"));
427        print_point($lon,$lat,$ref,$opt_p) if (($type eq "parcelle" || $type eq "trottoir" || $type eq "footpath"));
428        print_point($lon,$lat,$ref,$opt_t) if (($type eq "train"));
429        print_point($lon,$lat,$ref,$opt_l) if (($type eq "limite"));
430    }
431}
432
433for my $rel (@relations) {
434    if ($#{$rel->{"ways"}} >= 0) {
435        my $tag = 1;
436        my $file;
437        my $first_way = $ways[$rel->{"ways"}[0]];
438        my $type = $first_way->{"type"};
439
440        $file = $opt_b if (($type eq "building_nowall" || $type eq "building"));
441        $file = $opt_w if (($type eq "water"));
442        $file = $opt_r if (($type eq "riverbank"));
443        $file = $opt_p if (($type eq "parcelle" || $type eq "trottoir" || $type eq "footpath"));
444        $file = $opt_t if (($type eq "train"));
445        $file = $opt_l if (($type eq "limite"));
446
447        next unless (defined($file));
448
449        for my $ref_way (@{$rel->{"ways"}}) {
450            my $type = $ways[$ref_way]{"type"};
451            # N'écrit pas le tag pour un inner
452            print_way(abs($ref_way),$file,$type,($ref_way>=0));
453        }
454        if ($#{$rel->{"ways"}} >= 1) {
455            print {${$files{$file}}} "<relation id=\"" , -1-$rel->{"ref"} , "\">\n";
456            print {${$files{$file}}} " <tag k=\"type\" v=\"" , $rel->{"type"} , "\"/>\n";
457            print {${$files{$file}}} " <member type=\"way\" ref=\"" , -1-$rel->{"ways"}[0] , "\" role=\"outer\"/>\n";
458            my $ways = $rel->{"ways"};
459            foreach my $way (@{$ways}[1..$#{$ways}])
460            {
461                # L'orientation du polygone indique si on ajoute ou si on enlève
462                my ($role,$ref);
463                if ($way > 0) {
464                    $role = "outer";
465                    $ref = $way;
466                }
467                else {
468                    $role = "inner";
469                    $ref = -$way;
470                }
471                print {${$files{$file}}} " <member type=\"way\" ref=\"" , -1-$ref , "\" role=\"$role\"/>\n";
472            }
473            print {${$files{$file}}} "</relation>\n";
474        }
475    }
476}
477
478# On ferme les balises ouvertes
479print_files "</osm>";
Note: See TracBrowser for help on using the repository browser.