You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
457 lines
14 KiB
457 lines
14 KiB
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
|
|
);
|
|
|
|
# HexGrid::DEBUG();
|
|
|
|
$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};
|
|
|
|
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;
|
|
|
|
|
|
|
|
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 => HexGrid::to_id($path_name) . "-path",
|
|
tile_page => "$path_name/Tiles",
|
|
colour => $parsed_template->{named_params}{colour},
|
|
stroke_width => $parsed_template->{named_params}{stroke_width}
|
|
};
|
|
$path_specs{$path_name}{starts_from} = $parsed_template->{named_params}{starts_from}
|
|
if $parsed_template->{named_params}{starts_from};
|
|
$path_specs{$path_name}{ends_to} = $parsed_template->{named_params}{ends_to}
|
|
if $parsed_template->{named_params}{ends_to};
|
|
}
|
|
|
|
|
|
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];
|
|
}
|
|
my $path = $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 });
|
|
$path->{starts_from} = $HexGrid::DIR{$path_spec{starts_from}}
|
|
if $path_spec{starts_from};
|
|
$path->{ends_to} = $HexGrid::DIR{$path_spec{ends_to}}
|
|
if $path_spec{ends_to};
|
|
}
|
|
|
|
|
|
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);
|
|
}
|
|
|
|
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});
|
|
}
|
|
}
|
|
|
|
say STDERR "Rendering Region $region\'s grid";
|
|
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)
|
|
{
|
|
say STDERR "Rendering Location $location_name\'s grid";
|
|
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 = "<!DOCTYPE html>";
|
|
$html_builder .= "\n<html>\n<body>";
|
|
$html_builder .= "\n" . <<EOS;
|
|
<script>
|
|
function clickPin(pinId, containerId) {
|
|
let popup = document.getElementById(pinId + '-popup');
|
|
popup.style.visibility = popup.style.visibility == 'visible' ? 'hidden' : 'visible';
|
|
}
|
|
</script>
|
|
EOS
|
|
if($show_coords)
|
|
{
|
|
$html_builder .= <<EOS;
|
|
<script>
|
|
function toggleCoords(show) {
|
|
for (var elem of document.getElementsByClassName('coords')) {
|
|
elem.style.visibility = show ? 'visible' : 'hidden';
|
|
}
|
|
}
|
|
</script>
|
|
<label for="show-coords-checkbox">Show coordinates</label>
|
|
<input type="checkbox" checked id="show-coords-checkbox" onclick="toggleCoords(event.srcElement.checked)" />
|
|
EOS
|
|
}
|
|
$html_builder .= "\n" . $grid->render;
|
|
$html_builder .= "\n</body>\n</html>";
|
|
return $html_builder;
|
|
}
|
|
|