Ticket #1435: multipolygon.diff

File multipolygon.diff, 19.3 KB (added by R2D2_C3PO, 10 years ago)
  • orp-drawing.pm

     
    4949
    5050    foreach (@$selected)
    5151    {
    52         next unless (ref $_  eq 'way');
     52        next unless (ref $_ eq 'way'); # Draw lines doesn't care about multipolygons
    5353        next if (scalar(@{$_->{"nodes"}}) < 2);
    5454
    5555    # this is a special case for ways (e.g. rivers) where we honor a
     
    117117{
    118118    my ($linenode, $layer, $way, $class, $style) = @_;
    119119
     120    return if (ref $way eq 'multipolygon');
     121
    120122    # convenience variables
    121123    my $id = $way->{"id"};
    122124    my $nodes = $way->{"nodes"};
     
    221223OUTER:
    222224    foreach (@$selected)
    223225    {
    224         next unless (ref $_ eq 'way');
     226        next unless (ref $_ eq 'way' or ref $_ eq 'multipolygon');
     227        # Skip ways that are already rendered
     228        # because they are part of a multipolygon
     229        next if (ref $_ eq 'way' and defined $_->{"multipolygon"});
    225230
    226         my $points = [];
    227         foreach (@{$_->{"nodes"}})
    228         {
    229             push(@$points, [ $_->{"lat"}, $_->{"lon"} ]) if (defined($_->{"lat"}) && defined($_->{"lon"}));
     231        my $ways;
     232       
     233        if (ref $_ eq 'way') {
     234            $ways = [$_];
    230235        }
    231         my $path = make_path(@$points)."Z ";
    232 
    233         # find out if we're the "outer" or "inner" polygon of a "multipolygon" relation
    234         foreach my $relpair(@{$_->{"relations"}})
    235         {
    236             my ($role, $rel) = @$relpair;
    237             if (defined $rel->{"tags"}->{"type"} && $rel->{"tags"}->{"type"} eq "multipolygon" && defined $role && $role eq "outer")
     236        if (ref $_ eq 'multipolygon') {
     237            $ways = [@{$_->{"outer"}}, @{$_->{"inner"}}];
     238        }
     239       
     240        my $path = '';
     241        foreach my $way (@$ways) {
     242            my $points = [];
     243            foreach (@{$way->{"nodes"}})
    238244            {
    239                 # right, we are "outer" - find all "inner" ways of this relation
    240                 # and add them to our path
    241                 foreach my $relmember(@{$rel->{"members"}})
    242                 {
    243                     my ($role, $obj) = @$relmember;
    244                     if ($role eq "inner" && ref($obj) eq "way")
    245                     {
    246                         #debug(sprintf("collecting way %d as 'hole' in polygon %d",
    247                         #    $obj->{"id"}, $_->{"id"}));
    248                         $points = [];
    249                         foreach (@{$obj->{"nodes"}})
    250                         {
    251                             push(@$points, [ $_->{"lat"}, $_->{"lon"} ]) if (defined($_->{"lat"}) && defined($_->{"lon"}));
    252                         }
    253                         $path .= make_path(@$points)."Z";
    254                     }
    255                 }
     245                push(@$points, [ $_->{"lat"}, $_->{"lon"} ]) if (defined($_->{"lat"}) && defined($_->{"lon"}));
    256246            }
    257             if (defined $rel->{"tags"}->{"type"} && $rel->{"tags"}->{"type"} eq "multipolygon" && defined $role && $role eq "inner")
    258             {
    259                 # we are "inner" - if the corresponding "outer" poly is tagged
    260                 # the same as we are, then don't draw anything (legacy polygon
    261                 # support). otherwise draw normally.
    262                 foreach my $relmember(@{$rel->{"members"}})
    263                 {
    264                     my ($role, $obj) = @$relmember;
    265                     if ($role eq "outer" && ref($obj) eq "way")
    266                     {
    267                         next OUTER if (tags_subset($_, $obj));
    268                         last;
    269                     }
    270                 }
    271             }
     247            $path .= make_path(@$points)."Z ";
    272248        }
     249
    273250        $writer->emptyTag("path", "d" => $path, "style" => "fill-rule:evenodd");
    274251    }
    275252    $writer->endTag("g");
    276253}
    277254
    278 # returns true if the first has a subset of the second object's tags,
    279 # with some tags being ignored
    280 sub tags_subset
    281 {
    282     my ($first, $second) = @_;
    283     foreach my $tag(keys %{$first->{"tags"}})
    284     {
    285         next if ($tag =~ /^(name|created_by|note|layer|osmarender:areaCenterLat|osmarender:areaCenterLon|osmarender:areaSize)$/);
    286         return 0 unless defined($second->{'tags'}{$tag}) && $first->{'tags'}{$tag} eq $second->{'tags'}{$tag};
    287     }
    288     return 1;
    289 }
    290255
    291256# sub render_text($textnode, $text, $coordinates)
    292257#
     
    346311        my $text = substitute_text($textnode, $_);
    347312        if ($text ne '')
    348313        {
     314            # This function only works on pathes
     315            next if (ref $_ eq 'multipolygon');
     316
    349317            if (ref $_ eq 'node')
    350318            {
    351                 debug("draw node text '$text'") if ($debug->{"drawing"});
    352319                render_text($textnode, $text, [$_->{'lat'}, $_->{'lon'}]);
    353320            }
    354321            elsif (ref $_ eq 'way')
    355322            {
    356323                draw_text_on_path($textnode, $_, $text);
    357324            }
     325            else
     326            {
     327                debug("Unhandled type in draw_text: ".ref($_)) if ($debug->{"drawing"});
     328            }
    358329        }
    359330    }
    360331}
     
    581552        my $text = substitute_text($textnode, $_);
    582553        next unless $text ne '';
    583554
    584         if (ref $_ eq 'way')
     555        # Skip ways that are already rendered
     556        # because they are part of a multipolygon
     557        next if (ref $_ eq 'way' and defined $_->{"multipolygon"});
     558
     559        if (ref $_ eq 'way' or ref $_ eq 'multipolygon')
    585560        {
    586561            #Area
    587562            my $labelRelation = $labelRelations->{$_->{'id'}};
     
    600575                render_text($textnode, $text, $center);
    601576            }
    602577        }
    603         else
     578        elsif (ref $_ eq 'node')
    604579        {
    605580            #Node
    606581            render_text($textnode, $text, [$_->{'lat'}, $_->{'lon'}]);
    607582        }
     583        else
     584        {
     585            debug("Unhandled type in draw_area_text: ".ref($_)) if ($debug->{"drawing"});
     586        }
    608587    }
    609588}
    610589
     
    643622sub draw_symbols
    644623{
    645624    my ($symbolnode, $layer, $selected) = @_;
    646 
    647625    foreach(@$selected)
    648626    {
    649         if (ref $_ eq 'way')
     627        # Skip ways that are already rendered
     628        # because they are part of a multipolygon
     629        next if (ref $_ eq 'way' and defined $_->{"multipolygon"});
     630       
     631        if (ref $_ eq 'way' or ref $_ eq 'multipolygon')
    650632        {
    651633            #Area
    652634            my $labelRelation = $labelRelations->{$_->{'id'}};
     
    665647                draw_symbol($symbolnode, $projected);
    666648            }
    667649        }
    668         else
     650        elsif (ref $_ eq 'node')
    669651        {
    670652            #Node
    671653            my $projected = project([$_->{'lat'}, $_->{'lon'}]);
    672654            draw_symbol($symbolnode, $projected);
    673655        }
     656        else
     657        {
     658            debug("Unhandled type in draw_symbols: ".ref($_)) if ($debug->{"drawing"});
     659        }
    674660    }
    675661}
    676662
     
    696682sub draw_circles
    697683{
    698684    my ($circlenode, $layer, $selected) = @_;
    699 
    700685    foreach(@$selected)
    701686    {
    702         if (ref $_ eq 'way')
     687        # Skip ways that are already rendered
     688        # because they are part of a multipolygon
     689        next if (ref $_ eq 'way' and defined $_->{"multipolygon"});
     690       
     691        if (ref $_ eq 'way' or ref $_ eq 'multipolygon')
    703692        {
    704693            #Area
    705694            my $labelRelation = $labelRelations->{$_->{'id'}};
     
    727716                        [ 'type', 'ref', 'scale', 'smart-linecap', 'cx', 'cy' ]));
    728717            }
    729718        }
    730         else
     719        elsif (ref $_ eq 'node')
    731720        {
    732721            #Node
    733722            my $projected = project([$_->{'lat'}, $_->{'lon'}]);
     
    736725                'cy' => $projected->[1],
    737726                copy_attributes_not_in_list($circlenode, [ 'type', 'ref', 'scale', 'smart-linecap', 'cx', 'cy' ]));
    738727        }
     728        else
     729        {
     730            debug("Unhandled type in draw_circles: ".ref($_)) if ($debug->{"drawing"});
     731        }
    739732    }
    740733}
    741734
  • orp-bbox-area-center.pm

     
    1414sub find_area_center
    1515{
    1616    my $way = shift;
    17     my $nodes = $way->{'nodes'};
     17    my $nodes;
     18    if (ref $way eq 'way')
     19    {
     20        $nodes = $way->{'nodes'};
     21    }
     22    elsif (ref $way eq 'multipolygon')
     23    {
     24        foreach (@{$way->{'outer'}})
     25        {
     26            push(@$nodes, @{$_->{'nodes'}});
     27        }
     28    }
    1829    my $maxlat = -180;
    1930    my $maxlon = -180;
    2031    my $minlat = 180;
  • orp-select.pm

     
    2424    my $newsel = Set::Object->new();
    2525    foreach ($oldsel->members())
    2626    {
    27         next if defined($e) and ref($_) != $e;
     27        next if defined($e) and ref($_) ne $e and not ($e eq 'way' and ref($_) eq 'multipolygon');
    2828        $newsel->insert($_) unless defined($_->{"tags"});
    2929    }
    3030    return $newsel;
     
    3838
    3939    foreach ($oldsel->members())
    4040    {
    41         next if defined($e) and ref($_) != $e;
     41        next if defined($e) and ref($_) ne $e and not ($e eq 'way' and ref($_) eq 'multipolygon');
    4242        $newsel->insert($_) if defined($_->{"tags"});
    4343    }
    4444    return $newsel;
     
    5353outer:
    5454    foreach ($oldsel->members())
    5555    {
    56         next if defined($e) and ref($_) ne $e;
     56        next if defined($e) and ref($_) ne $e and not ($e eq 'way' and ref($_) eq 'multipolygon');
    5757        foreach my $value(values(%{$_->{"tags"}}))
    5858        {
    5959            if (defined($seek->{$value}))
     
    7575outer:
    7676    foreach ($oldsel->members())
    7777    {
    78         next if (defined($e) and ref($_) ne $e);
     78        next if defined($e) and ref($_) ne $e and not ($e eq 'way' and ref($_) eq 'multipolygon');
    7979        foreach my $key(@keys_wanted)
    8080        {
    8181            if (defined($_->{"tags"}->{$key}))
     
    9999outer:
    100100    foreach ($oldsel->members())
    101101    {
    102         next if defined($e) and ref($_) ne $e;
     102        next if defined($e) and ref($_) ne $e and not ($e eq 'way' and ref($_) eq 'multipolygon');
    103103        foreach my $key(@keys_wanted)
    104104        {
    105105            next outer if (defined($_->{"tags"}->{$key}));
     
    187187outer:
    188188    foreach ($oldsel->members())
    189189    {   
    190         next if defined($e) and ref($_) ne $e;
     190        next if defined($e) and ref($_) ne $e and not ($e eq 'way' and ref($_) eq 'multipolygon');
    191191        # determine whether we're comparing against the tags of the object
    192192        # itself or the tags selected with the "s" attribute.
    193193        my $tagsets;
  • orp.pl

     
    130130    "rules" => 0,    # print all rules and how many matches
    131131    "indexes" => 0,  # print messages about the use of indexes
    132132    "drawing" => 0,  # print out all drawing instructions executed
     133    "multipolygon" => 0, # print debugging messages for multipolygons
    133134};
    134135
    135136our $node_storage = {};
     
    217218my %parser_args = (Source => {SystemId => $input_file});
    218219$parser->parse(%parser_args);
    219220
    220 # initialise level-0 selection list with all available objects.
    221 # (relations are only there for specific reference; you cannot
    222 # have rules that match relations. if you want that, then add
    223 # relations to the initial selection here.)
    224 our $selection = [];
    225 $selection->[0] = Set::Object->new();
    226 $selection->[0]->insert(values(%$way_storage));
    227 $selection->[0]->insert(values(%$node_storage));
    228 
    229221# initialise the "ways" element of every node with the list of
    230222# ways it belongs to (creating a back reference)
     223# FIXME: it's not entirely clear if this should also be done for
     224# multipolygon objects. I think it should be done, but it needs
     225# further checking.
    231226foreach (values(%$way_storage))
    232227{
    233228    foreach my $node(@{$_->{"nodes"}})
     
    261256    }
    262257}
    263258
     259my $multipolygon_wayid = 0;
     260
     261sub assemble_closed_ways
     262{
     263    my ($inputways, $relation) = @_;
     264    return [] if not defined $inputways;
     265    my $outputways = [];
     266    while (@$inputways > 0)
     267    {
     268        # Start with the first item in the list
     269        my $way = shift @$inputways;
     270        my $nodes = [@{$way->{'nodes'}}];
     271        my $tags = {};
     272        my $relations = [@{$way->{'relations'}}]; # TODO: Make sure no duplicate entries are present
     273        while (my ($key, $val) = each(%{$way->{'tags'}}))
     274        {
     275            $tags->{$key}=$val;
     276        }
     277        $multipolygon_wayid += 1;
     278        my $wayobj = {
     279            'layer' => $way->{'layer'},
     280            'timestamp' => $way->{'timestamp'},
     281            'user' => $way->{'user'},
     282            'nodes' => $nodes,
     283            'relations' => $relations,
     284            'id' => "multipolygon$multipolygon_wayid",
     285            'tags' => $tags,
     286        };
     287        bless($wayobj, 'way');
     288
     289        # $found stores information if new node where found in
     290        # the last iteration though the nodelist
     291        # if no new nodes are found but the list is still not
     292        # empty there are 2 or more disjunct areas
     293        # 1 = nodes were found in the last iteration or this
     294        #       is the first iteration
     295        # 0 = no nodes were found, start a new way
     296        my $found = 1;
     297        while (@$inputways > 0 && $found)
     298        {
     299            $found = 0;
     300            foreach my $index (0 .. $#{$inputways})
     301            {
     302                my $nodelist = @$inputways[$index]->{'nodes'};
     303                my @sorted;
     304                # Check if the way's direction is reversed
     305                if (defined $nodes->[-1] && defined $nodelist->[-1] &&
     306                    ($nodes->[-1] eq $nodelist->[-1]))
     307                {
     308                    @sorted = reverse @$nodelist;
     309                }
     310                else
     311                {
     312                    @sorted = @$nodelist;
     313                }
     314
     315                # Check if the way matches
     316                if (defined $nodes->[-1] && defined $sorted[0] &&
     317                    ($nodes->[-1] eq $sorted[0]))
     318                {
     319                    # Add way segement
     320                    $found = 1;
     321                    # Add tags to taglist
     322                    while (my ($key, $val) = each(%{@$inputways[$index]->{'tags'}}))
     323                    {
     324                        $tags->{$key}=$val;
     325                    }
     326                    push(@$relations, @{@$inputways[$index]->{'relations'}});
     327                    # Remove first node which is identical to
     328                    # the last node of the old way
     329                    shift @sorted;
     330                    push(@$nodes, @sorted);
     331                    # Remove segment from the list of available segements
     332                    splice(@$inputways, $index, 1);
     333                    last;
     334                }
     335            }
     336        }
     337        push(@$outputways, $wayobj);
     338        debug("Unclosed way in multipolygon $relation->{'id'}") if (defined @$nodes[0]
     339            and @$nodes[0] ne @$nodes[-1] and $debug->{'multipolygon'});
     340    }
     341    return $outputways;
     342}
     343
     344# returns true if the first has a subset of the second object's tags,
     345# with some tags being ignored
     346sub tags_subset
     347{
     348    my ($first, $second) = @_;
     349    foreach my $tag(keys %{$first->{"tags"}})
     350    {
     351        next if ($tag =~ /^(name|created_by|note|layer|osmarender:areaCenterLat|osmarender:areaCenterLon|osmarender:areaSize)$/);
     352        return 0 unless defined($second->{'tags'}{$tag}) && $first->{'tags'}{$tag} eq $second->{'tags'}{$tag};
     353    }
     354    return 1;
     355}
     356
     357#Multipolygon:
     358# "outer": [way1, way2, ...]
     359#   Combined to form full ways
     360# "inner": [way1, way2, ...]
     361#   Combined to form full ways, for each inner way a new way-object with
     362#   the sum of all tags is created and added to $way_storage
     363# "tags":
     364#   The sum of all tags, from both the relation and all the outer ways.
     365# The original ways get a special tag, so they aren't processed anymore
     366# in function that are able to handle multipolygons.
     367#
     368# In theory each way could be represented as a multipolygon with the
     369# following properties:
     370# - One outer way
     371# - No inner ways
     372# - Tags from the original way
     373#
     374# This might be a good future enhancement but requires big changes to
     375# existing code.
     376#
     377# TODO:
     378# - Label relation
     379
     380
     381foreach my $rel (values(%$relation_storage))
     382{
     383    next unless defined $rel->{'tags'}->{'type'} and $rel->{'tags'}->{'type'} eq 'multipolygon';
     384    my $outerways = ();
     385    my $innerways = ();
     386    $multipolygon_wayid += 1;
     387    my $multipolygon = {
     388        'multipolgyon_relation' => $rel,
     389        'relations' => [],
     390        'tags' => {},
     391        'id' => "multipolygon$multipolygon_wayid"
     392    };
     393    # Copy tags from relation
     394    while (my ($key, $val) = each(%{$rel->{"tags"}}))
     395    {
     396        $multipolygon->{"tags"}->{$key}=$val;
     397    }
     398    foreach my $relmember(@{$rel->{"members"}})
     399    {
     400        my ($role, $obj) = @$relmember;
     401        next unless (defined $role && defined $obj &&
     402            ref($obj) eq "way" && defined $obj->{"nodes"});
     403        if ($role eq "outer")
     404        {
     405            push(@$outerways, $obj);
     406            push(@{$multipolygon->{'relations'}}, @{$obj->{'relations'}});
     407            while (my ($key, $val) = each(%{$obj->{'tags'}}))
     408            {
     409                $multipolygon->{'tags'}->{$key}=$val;
     410            }
     411        }
     412        elsif ($role eq 'inner')
     413        {
     414            push(@$innerways, $obj);
     415        }
     416        else
     417        {
     418            debug("Unknown role \"$role\" in multipolygon relation $rel->{'id'}!") if ($debug->{'multipolygon'});
     419        }
     420        $obj->{'multipolygon'} = 1; # Mark object as beeing part of a multipolygon
     421    }
     422    # A list of all outer and inner nodes is assembled, now sort them
     423    $multipolygon->{'outer'} = assemble_closed_ways($outerways, $rel);
     424    $multipolygon->{'inner'} = assemble_closed_ways($innerways, $rel);
     425    bless($multipolygon, 'multipolygon');
     426
     427    # Add inner ways to the global list of ways
     428    WAY:
     429    foreach my $way (@{$multipolygon->{'inner'}})
     430    {
     431        # Handle multipolygon in multipolygon
     432        foreach (@{$way->{'relations'}})
     433        {
     434            my ($role, $wayrelation) = @{$_};
     435            next WAY if ($role eq 'outer' and defined $wayrelation->{"tags"}->{"type"}
     436                and $wayrelation->{"tags"}->{"type"} eq "multipolygon")
     437        }
     438        # Handle old-style multipolygons
     439        next if tags_subset($way, $multipolygon);
     440        $way_storage->{$way->{'id'}} = $way;
     441    }
     442    # Add multipolygon object to the global list of ways
     443    $way_storage->{$multipolygon->{'id'}} = $multipolygon;
     444}
     445
     446# initialise level-0 selection list with all available objects.
     447# (relations are only there for specific reference; you cannot
     448# have rules that match relations. if you want that, then add
     449# relations to the initial selection here.)
     450our $selection = [];
     451$selection->[0] = Set::Object->new();
     452$selection->[0]->insert(values(%$way_storage));
     453$selection->[0]->insert(values(%$node_storage));
     454
    264455# initialise the tag indexes. These will help us to quickly
    265456# find objects that have a given tag key.
    266457foreach (values(%$way_storage))
     
    634825        # extract data into variables for convenience. the "points"
    635826        # array contains lat/lon pairs of the nodes.
    636827        my $way = $way_storage->{$way_id};
     828        debug('ERROR: Multipolygon in generate_paths!') if ref $way eq
     829            'multipolygon' and $debug->{'multipolygon'};
    637830        my $types = $referenced_ways{$way_id};
    638831        my $tags = $way->{"tags"};
    639832        my $points = [];