# # PERL mapweaver module by gary68 # # # # # Copyright (C) 2011, Gerhard Schwanz # # This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the # Free Software Foundation; either version 3 of the License, or (at your option) any later version. # # This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or # FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. # # You should have received a copy of the GNU General Public License along with this program; if not, see # package mwMulti ; use strict ; use warnings ; use mwMap ; use mwMisc ; use mwFile ; use mwLabel ; use mwConfig ; use mwRules ; use Math::Polygon ; use vars qw($VERSION @ISA @EXPORT @EXPORT_OK); require Exporter ; @ISA = qw ( Exporter AutoLoader ) ; @EXPORT = qw ( processMultipolygons ) ; my $newId = 0 ; my %multiNodes = () ; my %multiTags = () ; my %multiPaths = () ; my %wayUsed = () ; # ------------------------------------------------------------------------- sub processMultipolygons { my $notDrawnMP = 0 ; my $mp = 0 ; my $mpLabels = 0 ; my $mpNotDrawnLabels = 0 ; print "draw multipolygons...\n" ; preprocessMultipolygons() ; foreach my $multiId (keys %multiTags) { my $ruleRef = getAreaRule ( \@{$multiTags{$multiId}} ) ; if (defined $ruleRef) { my $svgText = "" ; my $icon = "" ; if ($$ruleRef{'icon'} ne "none") { $icon = $$ruleRef{'icon'} ; } else { my $col = $$ruleRef{'color'} ; $svgText = "fill=\"$col\" " ; } my $ref = $multiPaths{$multiId}[0] ; # first, outer way my $size = areaSize ( $ref ) ; if ($size >= cv('minareasize') ) { drawArea ($svgText, $icon, $multiPaths{$multiId}, 1, "multi") ; $mp++ ; # LABELS my $name = "" ; my $ref1 ; if ( cv('ignorelabels') eq "0" ) { ($name, $ref1) = createLabel ( $multiTags{$multiId}, $$ruleRef{'label'}, 0, 0) ; if ( ( $$ruleRef{'label'} ne "none") and ( cv('nolabel') eq "1" ) and ($name eq "") ) { $name = "NO LABEL" ; } } if ($name ne "") { if ($size >= cv('minarealabelsize') ) { $mpLabels++ ; if (cv('debug') eq "1") { print "MP LABEL: $name, size: $$ruleRef{'labelsize'}, color: $$ruleRef{'labelcolor'}\n" ; } my ($x, $y) = areaCenter ( $multiPaths{$multiId}[0] ) ; my $labelFont = $$ruleRef{'labelfont'} ; my $labelFontFamily = $$ruleRef{'labelfontfamily'} ; my $labelSize = $$ruleRef{'labelsize'} ; my $color = $$ruleRef{'labelcolor'} ; my $labelBold = $$ruleRef{'labelbold'} ; my $labelItalic = $$ruleRef{'labelitalic'} ; my $labelHalo = $$ruleRef{'labelhalo'} ; my $labelHaloColor = $$ruleRef{'labelhalocolor'} ; my $svgText = createTextSVG ( $labelFontFamily, $labelFont, $labelBold, $labelItalic, $labelSize, $color, $labelHalo, $labelHaloColor) ; # $svgText = createTextSVG ( undef, undef, $$ruleRef{'labelsize'}, $$ruleRef{'labelcolor'}, undef, undef ) ; if (cv('debug') eq "1") { print "MP LABEL: svg: \"$svgText\"\n" ; } placeLabelAndIcon ($x, $y, 1, 0, $name, $svgText, "none", 0, 0, "arealabels") ; } # if size else { $mpNotDrawnLabels++ ; } } else { } } else { $notDrawnMP++ ; } } # if rule } # foreach multi print "$mp multipolygon areas drawn, $notDrawnMP not drawn because they were too small.\n" ; print "$mpLabels multipolygon labels drawn, $mpNotDrawnLabels not drawn because belonging areas were too small.\n" ; } # ------------------------------------------------------------------------------------------ sub preprocessMultipolygons { # # preprecess all multipolygons # my ($wayNodesRef, $wayTagsRef) = getWayPointers() ; my ($relationMembersRef, $relationTagsRef) = getRelationPointers() ; foreach my $relId (keys %$relationMembersRef) { my $isMulti = 0 ; foreach my $tag (@{$$relationTagsRef{$relId}}) { if ( ($tag->[0] eq "type") and ($tag->[1] eq "multipolygon") ) { $isMulti = 1 ; } } if ($isMulti) { if (cv('debug') eq "1") { print "\n---------------------------------------------------\n" ; } if (cv('debug') eq "1") { print "\nRelation $relId is multipolygon!\n" ; } # get inner and outer ways my (@innerWays) = () ; my (@outerWays) = () ; foreach my $member ( @{$$relationMembersRef{$relId}} ) { if ( ($member->[0] eq "way") and ($member->[2] eq "outer") and (defined @{$$wayNodesRef{$member->[1]}} ) ) { push @outerWays, $member->[1] ; } if ( ($member->[0] eq "way") and ($member->[2] eq "inner") and (defined @{$$wayNodesRef{$member->[1]}} )) { push @innerWays, $member->[1] ; } } if (cv('debug') eq "1") { print "OUTER WAYS: @outerWays\n" ; } if (cv('debug') eq "1") { print "INNER WAYS: @innerWays\n" ; } my ($ringsWaysRef, $ringsNodesRef) ; my @ringWaysInner = () ; my @ringNodesInner = () ; my @ringTagsInner = () ; # build rings inner if (scalar @innerWays > 0) { ($ringsWaysRef, $ringsNodesRef) = buildRings (\@innerWays, 1) ; @ringWaysInner = @$ringsWaysRef ; @ringNodesInner = @$ringsNodesRef ; for (my $ring=0; $ring<=$#ringWaysInner; $ring++) { if (cv('debug') eq "1") { print "INNER RING $ring: @{$ringWaysInner[$ring]}\n" ; } my $firstWay = $ringWaysInner[$ring]->[0] ; if (scalar @{$ringWaysInner[$ring]} == 1) {$wayUsed{$firstWay} = 1 ; } # way will be marked as used/drawn by multipolygon @{$ringTagsInner[$ring]} = @{$$wayTagsRef{$firstWay}} ; # ring will be tagged like first contained way if (cv('debug') eq "1") { print "tags from first way...\n" ; foreach my $tag (@{$$wayTagsRef{$firstWay}}) { print " $tag->[0] - $tag->[1]\n" ; } } if ( (scalar @{$$wayTagsRef{$firstWay}}) == 0 ) { if (cv('debug') eq "1") { print "tags set to hole in mp.\n" ; } push @{$ringTagsInner[$ring]}, ["multihole", "yes"] ; } } } # build rings outer my @ringWaysOuter = () ; my @ringNodesOuter = () ; my @ringTagsOuter = () ; if (scalar @outerWays > 0) { ($ringsWaysRef, $ringsNodesRef) = buildRings (\@outerWays, 1) ; @ringWaysOuter = @$ringsWaysRef ; # not necessary for outer @ringNodesOuter = @$ringsNodesRef ; for (my $ring=0; $ring<=$#ringWaysOuter; $ring++) { if (cv('debug') eq "1") { print "OUTER RING $ring: @{$ringWaysOuter[$ring]}\n" ; } my $firstWay = $ringWaysOuter[$ring]->[0] ; if (scalar @{$ringWaysOuter[$ring]} == 1) {$wayUsed{$firstWay} = 1 ; } @{$ringTagsOuter[$ring]} = @{$$relationTagsRef{$relId}} ; # tags from relation if (cv('debug') eq "1") { print "tags from relation...\n" ; foreach my $tag (@{$$relationTagsRef{$relId}}) { print " $tag->[0] - $tag->[1]\n" ; } } if (scalar @{$$relationTagsRef{$relId}} == 1) { @{$ringTagsOuter[$ring]} = @{$$wayTagsRef{$firstWay}} ; # ring will be tagged like first way } } } # outer my @ringNodesTotal = (@ringNodesInner, @ringNodesOuter) ; my @ringWaysTotal = (@ringWaysInner, @ringWaysOuter) ; my @ringTagsTotal = (@ringTagsInner, @ringTagsOuter) ; processRings (\@ringNodesTotal, \@ringWaysTotal, \@ringTagsTotal) ; } # multi } # relIds } # ----------------------------------------------------------------------------------------- sub processRings { # # process rings of multipolygons and create path data for svg # my ($ref1, $ref2, $ref3) = @_ ; my @ringNodes = @$ref1 ; my @ringWays = @$ref2 ; my @ringTags = @$ref3 ; my @polygon = () ; my @polygonSize = () ; my @ringIsIn = () ; my @stack = () ; # all created stacks my %selectedStacks = () ; # stacks selected for processing my $actualLayer = 0 ; # for new tags # rings referenced by array index my ($lonRef, $latRef) = getNodePointers() ; my ($wayNodesRef, $wayTagsRef) = getWayPointers() ; # create polygons if (cv('debug') eq "1") { print "CREATING POLYGONS\n" ; } for (my $ring = 0 ; $ring <= $#ringWays; $ring++) { my @poly = () ; foreach my $node ( @{$ringNodes[$ring]} ) { push @poly, [$$lonRef{$node}, $$latRef{$node}] ; } my ($p) = Math::Polygon->new(@poly) ; $polygon[$ring] = $p ; $polygonSize[$ring] = $p->area ; if (cv('debug') eq "1") { print " POLYGON $ring - created, size = $polygonSize[$ring] \n" ; foreach my $tag (@{$ringTags[$ring]}) { print " $tag->[0] - $tag->[1]\n" ; } } } # create is_in list (unsorted) for each ring if (cv('debug') eq "1") { print "CALC isIn\n" ; } for (my $ring1=0 ; $ring1<=$#polygon; $ring1++) { my $res = 0 ; for (my $ring2=0 ; $ring2<=$#polygon; $ring2++) { if ($ring1 < $ring2) { $res = isIn ($polygon[$ring1], $polygon[$ring2]) ; if ($res == 1) { push @{$ringIsIn[$ring1]}, $ring2 ; if (cv('debug') eq "1") { print " $ring1 isIn $ring2\n" ; } } if ($res == 2) { push @{$ringIsIn[$ring2]}, $ring1 ; if (cv('debug') eq "1") { print " $ring2 isIn $ring1\n" ; } } } } } if (cv('debug') eq "1") { print "IS IN LIST\n" ; for (my $ring1=0 ; $ring1<=$#ringNodes; $ring1++) { if (defined @{$ringIsIn[$ring1]}) { print " ring $ring1 isIn - @{$ringIsIn[$ring1]}\n" ; } } print "\n" ; } # sort is_in list, biggest first if (cv('debug') eq "1") { print "SORTING isIn\n" ; } for (my $ring=0 ; $ring<=$#ringIsIn; $ring++) { my @isIn = () ; foreach my $ring2 (@{$ringIsIn[$ring]}) { push @isIn, [$ring2, $polygonSize[$ring2]] ; } @isIn = sort { $a->[1] <=> $b->[1] } (@isIn) ; # sorted array my @isIn2 = () ; # only ring numbers foreach my $temp (@isIn) { push @isIn2, $temp->[0] ; } @{$stack[$ring]} = reverse (@isIn2) ; push @{$stack[$ring]}, $ring ; # sorted descending and ring self appended if (cv('debug') eq "1") { print " stack ring $ring sorted: @{$stack[$ring]}\n" ; } } # find tops and select stacks if (cv('debug') eq "1") { print "SELECTING STACKS\n" ; } my $actualStack = 0 ; for (my $stackNumber=0 ; $stackNumber<=$#stack; $stackNumber++) { # look for top element my $topElement = $stack[$stackNumber]->[(scalar @{$stack[$stackNumber]} - 1)] ; my $found = 0 ; for (my $stackNumber2=0 ; $stackNumber2<=$#stack; $stackNumber2++) { if ($stackNumber != $stackNumber2) { foreach my $ring (@{$stack[$stackNumber2]}) { if ($ring == $topElement) { $found = 1 ; if (cv('debug') eq "1") { print " element also found in stack $stackNumber2\n" ; } } } } } if ($found == 0) { @{$selectedStacks{$actualStack}} = @{$stack[$stackNumber]} ; $actualStack++ ; if (cv('debug') eq "1") { print " stack $stackNumber has been selected.\n" ; } } } # process selected stacks if (cv('debug') eq "1") { print "PROCESS SELECTED STACKS\n" ; } # while stacks left while (scalar (keys %selectedStacks) > 0) { my (@k) = keys %selectedStacks ; if (cv('debug') eq "1") { print " stacks available: @k\n" ; } my @nodes = () ; my @nodesOld ; my @processedStacks = () ; # select one bottom element my $key = $k[0] ; # key of first stack if (cv('debug') eq "1") { print " stack nr $key selected\n" ; } my $ringToDraw = $selectedStacks{$key}[0] ; if (cv('debug') eq "1") { print " ring to draw: $ringToDraw\n" ; } push @nodesOld, @{$ringNodes[$ringToDraw]} ; # outer polygon push @nodes, [@{$ringNodes[$ringToDraw]}] ; # outer polygon as array # and remove ring from stacks; store processed stacks foreach my $k2 (keys %selectedStacks) { if ($selectedStacks{$k2}[0] == $ringToDraw) { shift (@{$selectedStacks{$k2}}) ; push @processedStacks, $k2 ; if (scalar @{$selectedStacks{$k2}} == 0) { delete $selectedStacks{$k2} ; } if (cv('debug') eq "1") { print " removed $ringToDraw from stack $k2\n" ; } } } # foreach stack in processed stacks foreach my $k (@processedStacks) { # if now bottom of a stack is hole, then add this polygon to points if (defined $selectedStacks{$k}) { my $tempRing = $selectedStacks{$k}[0] ; my $temp = $ringTags[$tempRing]->[0]->[0] ; if (cv('debug') eq "1") { print " testing for hole: stack $k, ring $tempRing, tag $temp\n" ; } if ($ringTags[$tempRing]->[0]->[0] eq "multihole") { push @nodesOld, @{$ringNodes[$tempRing]} ; push @nodes, [@{$ringNodes[$tempRing]}] ; # print " nodes so far: @nodes\n" ; # and remove this element from stack shift @{$selectedStacks{$k}} ; if (scalar @{$selectedStacks{$k}} == 0) { delete $selectedStacks{$k} ; } if (cv('debug') eq "1") { print " ring $tempRing identified as hole\n" ; } } } } # add way @{$multiNodes{$newId}} = @nodesOld ; @{$multiTags{$newId}} = @{$ringTags[$ringToDraw]} ; @{$multiPaths{$newId}} = @nodes ; push @{$$wayTagsRef{$newId}}, ["layer", $actualLayer] ; $actualLayer++ ; if (cv('debug') eq "1") { print " DRAWN: $ringToDraw, wayId $newId\n" ; foreach my $tag (@{$ringTags[$ringToDraw]}) { print " k/v $tag->[0] - $tag->[1]\n" ; } } $newId++ ; } # (while) } 1 ;