use v5.36; use rlib '.'; use HexGrid; use HexGrid::Pin; use MWTemplate; use MediaWiki::API; use Getopt::Long; use Carp; use Data::Dumper; # $Data::Dumper::Indent = 1; use feature "signatures"; no warnings "experimental::signatures"; # This regex is a whitespace forgiving version of /^(-?\d+),(-?\d+)/, an int pair my $coords_regex = qr/^\s*(\s*-?\d+\s*)\s*,\s*(\s*-?\d+\s*)\s*$/; my $api_url; my $region_template_name = "MapRegion"; my $subregion_template_name = "MapSubregion"; my $location_template_name = "MapLocation"; my $site_template_name = "MapSite"; my $path_template_name = "MapPath"; my $border_width = 1; my $border_colour = 'black'; my $default_path_stroke_width = 5; my $show_coords = 0; my $embed_images = 1; my $html_document = 1; my $outfile = '-'; my $regiondir; GetOptions ( 'api-url=s' => \$api_url, 'region-template-name=s' => \$region_template_name, 'subregion-template-name=s' => \$subregion_template_name, 'location-template-name=s' => \$location_template_name, 'site-template-name=s' => \$site_template_name, 'border-width|bw=f' => \$border_width, 'border-colour|border-color|bc=s' => \$border_colour, 'show-coords|coords!' => \$show_coords, 'embed-images!' => \$embed_images, 'html-document!' => \$html_document, 'outfile=s' => \$outfile, 'regiondir=s' => \$regiondir ); $api_url // croak "Base API URL is required! Use --api-url to set"; my $grid = HexGrid->new(embed_images => $embed_images, defaults => { style => { 'stroke-width' => $border_width, stroke => $border_colour }, show_coords => $show_coords}); my %regions_by_subregion; my %images_for_region_grids; my %location_grids; my $mw = MediaWiki::API->new(); $mw->{config}->{api_url} = $api_url; say STDERR "Getting Region pages"; my $region_query_results = $mw->api ( { action => 'query', generator => 'categorymembers', gcmtitle => 'Category:Regions', gcmlimit => 'max', prop => 'info|revisions', rvprop => 'content', inprop => 'url', } ) || croak $mw->{error}->{code} . ': ' . $mw->{error}->{details}; my (@tile_pages, %background_pages); foreach my $page (values %{$region_query_results->{query}{pages}}) { next if $page->{title} =~ /^Category:/; my $region = $grid->make_region($page->{title}); my $parsed_template = MWTemplate::Parse($page->{revisions}[0]{'*'}, $region_template_name); next unless $parsed_template; say STDERR "Processing region: $page->{title}"; $region->{defaults}{colour} = $parsed_template->{named_params}{colour}; $region->{defaults}{coords_colour} = $parsed_template->{named_params}{coordinates_colour} if $parsed_template->{named_params}{coordinates_colour}; if($regiondir) { $regions_by_subregion{$region->{name}} = $region->{name}; } push @tile_pages, "$page->{title}/Tiles"; push @{$background_pages{"File:$parsed_template->{named_params}{background}"}}, $region; } say STDERR "Getting Subregion pages"; my $subregion_query_results = $mw->api ( { action => 'query', generator => 'categorymembers', gcmtitle => 'Category:Subregions', gcmlimit => 'max', prop => 'info|revisions', rvprop => 'content', inprop => 'url', } ) || croak $mw->{error}->{code} . ': ' . $mw->{error}->{details}; foreach my $page (values %{$subregion_query_results->{query}{pages}}) { next if $page->{title} =~ /^Category:/; my $parsed_template = MWTemplate::Parse($page->{revisions}[0]{'*'}, $subregion_template_name); next unless $parsed_template; say STDERR "Processing subregion: $page->{title}"; my $subregion = $grid->make_region($page->{title}); $subregion->{defaults}{colour} = $parsed_template->{named_params}{colour}; $subregion->{defaults}{coords_colour} = $parsed_template->{named_params}{coordinates_colour} if $parsed_template->{named_params}{coordinates_colour}; if($regiondir) { my $region_name = $parsed_template->{positional_params}[0]; $regions_by_subregion{$subregion->{name}} = $region_name; } push @tile_pages, "$page->{title}/Tiles"; push @{$background_pages{"File:$parsed_template->{named_params}{background}"}}, $subregion; } my @location_continuations; say STDERR "Getting Location pages"; my $location_query_results = $mw->api ( { action => 'query', generator => 'categorymembers', gcmtitle => 'Category:Locations', gcmlimit => 'max', prop => 'info|revisions', rvprop => 'content', inprop => 'url', } ) || croak $mw->{error}->{code} . ': ' . $mw->{error}->{details}; foreach my $page (values %{$location_query_results->{query}{pages}}) { next if $page->{title} =~ /^Category:/; my $parsed_template = MWTemplate::Parse($page->{revisions}[0]{'*'}, $location_template_name); next unless $parsed_template; say STDERR "Processing location: $page->{title}"; my $location = $grid->make_region($page->{title}); $location->{defaults}{colour} = $parsed_template->{named_params}{colour}; $location->{defaults}{coords_colour} = $parsed_template->{named_params}{coordinates_colour} if $parsed_template->{named_params}{coordinates_colour}; push @{$background_pages{"File:$parsed_template->{named_params}{background}"}}, $location; my $region_name = $parsed_template->{positional_params}[1]; $regions_by_subregion{$location->{name}} = $region_name; push @location_continuations, sub { return unless $parsed_template->{positional_params}[0] =~ $coords_regex; my ($nw, $sw) = ($1, $2); $location->make_tile_at($nw, $sw); if($regiondir) { my @coords_list = ({ nw => $nw, sw => $sw}); foreach my $coords (split /;/, $parsed_template->{named_params}{context_tiles}) { do { carp "Skipping bad spec: $coords"; next; } unless $coords =~ $coords_regex; push @coords_list, { nw => $1, sw => $2 }; } my $location_grid = $grid->subgrid_for_tiles(@coords_list); $location_grid->iter_tile( sub($tile) { # if haven't added image to grid yet, do so if($tile->image && !(exists $location_grid->{images}{$tile->image})) { $location_grid->{images}{$tile->image} = $grid->{images}{$tile->image}; } }); $location_grids{$location->{name}} = $location_grid; } }; } say STDERR "Getting Background image pages"; my $background_query_results = $mw->api({ action => 'query', prop => 'imageinfo', titles => join('|', keys %background_pages), iiprop => 'url' }) || carp $mw->{error}->{code} . ': ' . $mw->{error}->{details}; # say STDERR Dumper(\%background_pages); foreach my $page (values %{$background_query_results->{query}{pages}}) { if($page->{imageinfo}) { say STDERR "Processing image: $page->{title}"; $grid->add_image(HexGrid::to_id($page->{title}), $page->{imageinfo}[0]{url}); foreach my $subregion (@{$background_pages{$page->{title}}}) { $subregion->{defaults}{image} = HexGrid::to_id($page->{title}); if($regiondir) { my $region_name = $regions_by_subregion{$subregion->{name}}; # say STDERR $page->{title} unless $region_name; # say STDERR "$region_name"; push @{$images_for_region_grids{$region_name}}, { name => HexGrid::to_id($page->{title}), source => $page->{imageinfo}[0]{url} }; } } } } say STDERR "Getting Tile pages"; my $tile_query_results = $mw->api ( { action => 'query', titles => join('|', @tile_pages), prop => 'revisions', rvprop => 'content', } ) || croak $mw->{error}->{code} . ': ' . $mw->{error}->{details}; foreach my $page (values %{$tile_query_results->{query}{pages}}) { my $content = $page->{revisions}[0]{'*'}; my ($region_name) = $page->{title} =~ /(.*)\/Tiles/; say STDERR "Processing tiles for: $region_name"; my $region = $grid->{regions}{$region_name}; foreach my $coords (split /;/, $content) { do { carp "Skipping bad spec: $coords"; next; } unless $coords =~ $coords_regex; $region->make_tile_at($1,$2); } } say STDERR "Continuing Location processing"; $_->() for @location_continuations; say STDERR "Getting Site pages"; my $site_query_results = $mw->api ( { action => 'query', generator => 'categorymembers', prop => 'info|revisions', gcmtitle => 'Category:Sites', gcmlimit => 'max', rvprop => 'content', inprop => 'url', } ) || croak $mw->{error}->{code} . ': ' . $mw->{error}->{details}; foreach my $site_page_ref (values %{$site_query_results->{query}{pages}}) { next if $site_page_ref->{title} =~ /^Category:/; my $site_name = $site_page_ref->{title}; say STDERR "Processing Site $site_name"; my $site_url = $site_page_ref->{canonicalurl}; my $site_content = $site_page_ref->{revisions}[0]{'*'}; my $parsed_template = MWTemplate::Parse($site_content, $site_template_name); next unless $parsed_template; my ($nw,$sw) = split /,/, $parsed_template->{named_params}{coords}; my $tile = $grid->get_tile_at($nw, $sw); unless($tile) { carp "Coordinates of Site $site_name do not appear in the grid, skipping."; next; } my $imageinfo_query_results = $mw->api({ action => 'query', prop => 'imageinfo', titles => "File:$parsed_template->{named_params}{icon}", iiprop => 'url' }) || carp $mw->{error}->{code} . ': ' . $mw->{error}->{details}; my %image_pages = %{$imageinfo_query_results->{query}{pages}}; my $image_url = (values %image_pages)[0]{imageinfo}[0]{url}; $grid->add_image(HexGrid::to_id($parsed_template->{named_params}{icon}), $image_url); my $pin = HexGrid::Pin->new ( name => $site_name, id => HexGrid::to_id($site_name), icon => HexGrid::to_id($parsed_template->{named_params}{icon}), link => $site_url, description => $parsed_template->{named_params}{abstract} ); $tile->pin($pin); } my (%path_specs); say STDERR "Getting Path pages"; my $path_query_results = $mw->api ( { action => 'query', generator => 'categorymembers', prop => 'info|revisions', gcmtitle => 'Category:Paths', gcmlimit => 'max', rvprop => 'content', inprop => 'url', } ) || croak $mw->{error}->{code} . ': ' . $mw->{error}->{details}; foreach my $path_page_ref (values %{$path_query_results->{query}{pages}}) { next if $path_page_ref->{title} =~ /^Category:/; my $path_name = $path_page_ref->{title}; say STDERR "Processing Path $path_name"; my $path_url = $path_page_ref->{canonicalurl}; my $path_content = $path_page_ref->{revisions}[0]{'*'}; my $parsed_template = MWTemplate::Parse($path_content, $path_template_name); next unless $parsed_template; $path_specs{$path_name} = { id => "$path_name-path", tile_page => "$path_name/Tiles", colour => $parsed_template->{named_params}{colour}, stroke_width => $parsed_template->{named_params}{stroke_width} }; } say STDERR "Getting Path Tile pages"; my $path_tile_query_results = $mw->api ( { action => 'query', titles => join('|', map { $_->{tile_page} } values %path_specs), prop => 'revisions', rvprop => 'content', } ) || croak $mw->{error}->{code} . ': ' . $mw->{error}->{details}; foreach my $page (values %{$path_tile_query_results->{query}{pages}}) { my $content = $page->{revisions}[0]{'*'}; my ($path_name) = $page->{title} =~ /(.*)\/Tiles/; say STDERR "Processing tiles for: $path_name"; my %path_spec = %{$path_specs{$path_name}}; my @path_coords; foreach my $coords (split /;/, $content) { do { carp "Skipping bad spec: $coords"; next; } unless $coords =~ $coords_regex; push @path_coords, [$1,$2]; } $grid->make_path_from($path_spec{id}, \@path_coords, css_class => 'path', colour => $path_spec{colour}, style => { 'stroke-width' => $path_spec{stroke_width} // $default_path_stroke_width }); } open (my $fh, "> $outfile") or croak "Couldn't open $outfile for writing: $!"; say $fh ($html_document ? wrap_in_html($grid) : $grid->render); close $fh; if($regiondir) { chdir $regiondir || croak "Couldn't chdir to $regiondir: $!"; my $extension = $html_document ? 'html' : 'svg'; my %region_grid_listings; while(my ($subregion, $region) = each %regions_by_subregion) { push @{$region_grid_listings{$region}}, $subregion; } while(my ($region, $subregions) = each %region_grid_listings) { my $region_grid = $grid->subgrid_for_regions(@$subregions); # say STDERR Dumper($images_for_region_grids{$region}); if(exists $images_for_region_grids{$region}) { foreach my $image (@{$images_for_region_grids{$region}}) { # say STDERR Dumper($image); $region_grid->{images}{$image->{name}} = $grid->{images}{$image->{name}}; # $region_grid->add_image($image->{name}, $image->{source}); } } open (my $region_fh, "> $region.$extension") or croak "Couldn't open $region.extension for writing: $!"; say $region_fh ($html_document ? wrap_in_html($region_grid) : $region_grid->render); close $region_fh; } # Location grids need to import images while(my ($location_name, $location_grid) = each %location_grids) { open (my $location_fh, "> $location_name.$extension") or croak "Couldn't open $location_name.extension for writing: $!"; say $location_fh ($html_document ? wrap_in_html($location_grid) : $location_grid->render); close $location_fh; } } sub wrap_in_html($grid) { my $html_builder = ""; $html_builder .= "\n\n
"; $html_builder .= "\n" . <