Geo::G...Object(3pm)

πŸ“¦
version 0.06 Β· 2 versions
  • 0.06 Β· debian bookworm, debian trixie, ubuntu noble, ubuntu resolute, ubuntu stonking (latest)
  • 0.06 Β· ubuntu jammy
, in debian bookworm, debian trixie, ubuntu noble, ubuntu resolute, ubuntu stonking

✨ Geo::Google::MapObject ✨

the manual name is Geo::G...Object(3pm) β€” user contributed perl documentation, bestie, and yes it's giving ✨server side Google Maps✨ energy πŸ’…


πŸŽ€ NAME πŸŽ€

Geo::Google::MapObject β€” the code that helps you out with managing the server side of the Google Maps API πŸ—ΊοΈβœ¨


🌸 VERSION 🌸

Version 0.06 πŸ’–


πŸ¦‹ SYNOPSIS πŸ¦‹

okay so here's the whole vibe, no cap:

use HTML::Template::Pluggable;
use HTML::Template::Plugin::Dot;
use Geo::Google::MapObject;
my $map = Geo::Google::MapObject->new(
    key => 'ABQFbHAATHwok56Qe3MBtg0s7lgkHBS9HKneet7v0OIFhIwnBhTEGCHLTRRRBa_lUOCy1fDamS5PQt8qULYfYQ',
    zoom => 13,
    size => '512x400',
    maptype => 'terrain',
    markers=>
    [
            {
                    location=>'51.242844,0.011716',
                    color=>'green',
                    label=>'P',
                    title=>'Periapt Technologies',
                    href=>'http://www.periapt.co.uk'
            },
            {
                    location=>'51.243757,0.006051',
                    color=>'red',
                    label=>'C',
                    title=>'Crown Roast Butchers',
                    href=>'http://www.crownroast.co.uk/'
            },
    ]
);
my $template = HTML::Template::Pluggable->new(file=>'map.tmpl');
$template->param(map=>$map);
return $template->output;

like… you make the map object, you shove it into the template, you output the template. that's the whole loop. iconic behavior honestly πŸ‘‘


πŸ’œ DESCRIPTION πŸ’œ

so this module? it's here to give you a server side solution to working with the Google Maps API 🫢. and specifically β€” the way that an object of this class works is it encapsulates a "map" object that provides support for:

  • the static maps API πŸ—ΊοΈ
  • the javascript maps API ⚑
  • AJAX calls πŸ“‘
  • and non-javascript fallback data πŸ™ˆ

…but without making many assumptions about whatever framework you've got going on around it. periodt. πŸ’…

ngl there is one assumption though: we do assume you're using a template framework with support for a "dot" notation β€” for example HTML::Template::Pluggable. iykyk 🌸

and like, a genuinely important commitment of this module (main character trait fr) is support for graceful and consistent fallback to a functional non-javascript web page. we care about accessibility bestie, we're not monsters πŸ˜­πŸ’–


⚠️ the zoom/center situation (this is the drama) ⚠️

okay so here's the tea 🍡 β€” the javascript and static Google map APIs do not behave in quite the same way when zoom and center are not specified.

so to compensate for all that mess, the module gives you a choice between:

  1. specifying the center and zoom levels 🎯
  2. allowing the APIs and client side code to do whatever they think best πŸ¦‹ (chaotic, honestly, but valid)
  3. using a built in algorithm to calculate a sensible zoom and center 🧚
  4. and finally, supplying ones own algorithm to calculate a sensible zoom and center πŸ’…βœ¨

so yeah, pick your fighter 🫢

πŸ’– INTERFACE πŸ’–

new ✨

okay so here's everything you can hand the constructor, bestie, and yes we're going through ALL of them, no skipping 🫢

  • autozoom β€” if you don't set a center and/or zoom, this lil parameter can work them out for you by averaging the markers together. cute, right? If you give it an integer between 0 and 21, that number becomes the maximum zoom level and the builtin algorithm calculate_zoom_and_center gets called with that max zoom. If you pass a CODE ref instead, that function gets used β€” and it has to take an ARRAY ref of marker specifications as input, and it must return a pair: the zoom level as an integer, followed by a latitude-longitude string for the center. Finally, if the argument is blessed and has a calculate_zoom_and_center method, that gets used. ⚠️ heads up: for AUTO calculation of the center or zoom, all the markers must be in the form "decimal,decimal". The json method will delete this, because it has no meaning once the zoom level and center have been determined. bye πŸ‘‹
  • center β€” this has to be a location the Google maps API would recognize. If it's absent, and no autozoom has been set, then the Google maps API and the client side javascript code have to work out a center between them. teamwork πŸ’•
  • zoom β€” the zoom level (<http://code.google.com/apis/maps/documentation/staticmaps/#Zoomlevels>), which is a number between 0 and 21 inclusive. If it's absent (with no autozoom set) the API and client will need to set it themselves.
  • size β€” the size (<http://code.google.com/apis/maps/documentation/staticmaps/#Imagesizes>) has to be either a string of two numbers joined by the symbol "x", or a hash ref with width and height parameters. Either way, first number = width, second = height. If it's absent, the Google API gets to set the size however it sees fit. main character energy, honestly πŸ‘‘
  • format β€” optional, (<http://code.google.com/apis/maps/documentation/staticmaps/#ImageFormats>), and it must be one of 'png8', 'png', 'png32', 'gif', 'jpg', 'jpg-baseline'. If absent, the Google API sets the format as it sees fit. Note: even though the json method will pass this on, it seems to have no meaning in the dynamic API. it's giving decorative 😭
  • maptype β€” must be one of 'roadmap', 'satellite', 'terrain', 'hybrid', as described in <http://code.google.com/apis/maps/documentation/staticmaps/#MapTypes>. The json method translates these into numerical codes β€” roadmap => 0, satellite => 1, terrain => 2 and hybrid => 3 β€” because that makes it easy for the javascript code (<http://code.google.com/apis/maps/documentation/reference.html#GMapType.Constants>) to translate that numerical code into the client side codes. For example:
  var mapping = new Array(G_NORMAL_MAP, G_SATELLITE_MAP, G_PHYSICAL_MAP, G_HYBRID_MAP);
  var maptype = mapping[data.maptype];
  • mobile β€” should be either the string 'true' or 'false', and it defaults to 'false'. The static API uses it to provide more robust tiles as described in <http://code.google.com/apis/maps/documentation/staticmaps/#Mobile>. However, it seems to have no meaning for the dynamic API. πŸ’€
  • key β€” the mandatory API key. You can sign up for one at <http://code.google.com/apis/maps/signup.html>. no key, no map, periodt πŸ”‘
  • sensor β€” should be either the string 'true' or 'false', defaults to 'false'. Both versions of the Google API require it (<http://code.google.com/apis/maps/documentation/staticmaps/#Sensor>), and this module reflects that by using it in the static_map_url and javascript_url methods.
  • markers β€” if present, the markers should be an array ref of marker specifications, so it can be used in a TMPL_LOOP. Each marker specification should be a hash ref and may contain whatever keys the javascript client side code needs. It must, however, have a "location" field, and that has to be interpretable by the Google API as a location. In general that means the location must be a string of the form "decimal,decimal", where the first decimal is the latitude and the second is the longitude. Other fields might include, for example: id, title, href, icon. Generally these just get passed straight through by the json method and should be used by the client side code as it sees fit. Three fields that have a special meaning β€” "color", "size", "label" β€” are the ones described by the static maps API (<http://code.google.com/apis/maps/documentation/staticmaps/#MarkerStyles>). The GIcon class (<http://code.google.com/apis/maps/documentation/reference.html#GIcon>) contains lots of ways of specialising the look and feel of the markers in the dynamic API, but the author can find no easy way of consistently replicating the static marker styles in the dynamic API. πŸ™ˆ The only other field that gets special treatment is "title". The json method will encode the title field. So for example, if a marker has a title field of 'Scloß', then if the javascript code uses this as the title attribute when creating a GMarker, hovering the mouse over that marker will display the German word for castle. 🏰 slay
  • hl β€” this parameter specifies the language to be used. If absent, the API will select the language. Only the dynamic javascript API seems to use this parameter, as described in <http://code.google.com/apis/maps/documentation/#Localization>. As such, it's used in the javascript_url method. 🌈

calculate_zoom_and_center πŸ”

This function takes a reference to an array of marker specifications and a maximum zoom level, and returns a pair consisting of a suggested zoom level and a center.

The algorithm works by iteratively building the following objects:

  • A set of points, starting with the first element of the array and adding the next one at each iteration.
Actually we don't actually have a variable for this, but without thinking about this set conceptually I cannot state the algorithm. The set starts off consisting of just the first marker location.
  • ($ctheta, $cphi) β€” these numbers represent a central point of the set of points, expressed as radians. The algorithm does not guarantee that the point is the true centre, but we treat it as the centre of a circle. This starts off by taking the first element and converting its latitude and longitude to radians.
  • $radius β€” this variable is initialised to 0.
  • The contract β€” the algorithm requires that at each iteration all the points in the set are at most $radius radians from ($ctheta, $cphi).
  • The iteration β€” we look at the next point. We look at the distance ($distance) between the new point and ($ctheta, $cphi). If this is less than or equal to $radius, the iteration is over. Otherwise the following happens:
    ($ctheta, $cphi)      = mid point of ($ctheta, $cphi) and the new point
    $radius               = $radius + 0.5 * distance between ($ctheta, $cphi) and the new point
  • At the end β€” we convert ($ctheta, $cphi) back to latitude and longitude, and logarithmically convert the radius to a zoom level.
The author doubts that this algorithm is optimal, but it seems quick, uses only one pass through the markers, and can be seen in action at <http://testmaps.periapt.co.uk>. Also, if you have a better one, you can specify it by passing a suitable CODE ref or blessed scalar to the autozoom parameter of the constructor. πŸ’…

static_map_url πŸ—ΊοΈ

Returns a URL suitable for use with the static maps API, based upon the arguments passed to the constructor.

javascript_url πŸ“œ

Returns a URL suitable for use in loading the dynamic map API.

markers πŸ“

This returns the marker array ref.

json 🧚

This function uses the JSON module to return a JSON representation of the object. It removes the API key, because that should not be required by any javascript client side code. If any marker object has a title attribute, then that attribute is encoded so it will display correctly during mouse overs. ✨

width πŸ“

This returns the width of the image, or undef if none has been set.

height πŸ“

This returns the height of the image, or undef if none has been set.


πŸ¦‹ TUTORIAL πŸ¦‹

This module only covers the server side of a two way conversation between a server and a talkative client. So the tutorial has to also consider HTML and javascript. And to understand this tutorial, we assume at least a passing familiarity with the following:

The markup πŸŽ€

We probably have a template file, map.tmpl, something like the following:

  <html>
    <head>
      <title>Test Map Tutorial</title>

      <!-- This way we tell the javascript what URL to contact the server with. -->
      <link href="/map_json" id="get_init_data"/>

      <!-- This will be filled in with the correct URL to load the Google maps javascript API. -->
      <script type="text/javascript" src="<TMPL_VAR NAME="maps.javascript_url()">"></script>

      <!-- This will load the yui API. If you use a different javascript API this would obviously change. -->
      <script type="text/javascript" src="http://yui.yahooapis.com/2.8.0r4/build/yahoo-dom-event/yahoo-dom-event.js"></script>
      <script type="text/javascript" src="http://yui.yahooapis.com/2.8.0r4/build/connection/connection.js"></script>
      <script type="text/javascript" src="http://yui.yahooapis.com/2.8.0r4/build/json/json-min.js"></script>

      <!-- This loads our javascript code. -->
      <script type="text/javascript" src="/js/maps.js"></script>
    </head>
    <body>

      <!-- This div will be completely replaced by the Google API and is identified by #map_canvas. -->
      <div id="map_canvas">

        <!-- This image will be loaded by the static Google map API. It will be available whilst the
                javascript based map is loading and if javascript is turned off
        -->
        <div><img
                alt="Test map"
                src="<TMPL_VAR NAME="maps.static_map_url()">"
                width="<TMPL_VAR NAME="maps.width()">"
                height="<TMPL_VAR NAME="maps.height()">"
                />
        </div>

...and then the markup keeps going, bestie, because right after all the map canvas vibes comes the glow-up for everyone whose browser is out here like "javascript? never heard of her" πŸ’…

<!-- The following will be used if javascript is disabled.
     We can use it display information that would normally be obtained by
     hovering over or clicking on markers on the dynamic map display.
-->
<noscript>
     <table>
       <TMPL_LOOP NAME="maps.markers()">
         <tr>
           <td class="google_<TMPL_VAR NAME="this.color">"><TMPL_VAR NAME="this.label"></td>
           <td>
              <TMPL_IF NAME="this.href">
              <a href="<TMPL_VAR NAME="this.href">">
              </TMPL_IF>
              <TMPL_VAR NAME="this.title">
              <TMPL_IF NAME="this.href">
              </a>
              </TMPL_IF>
            </td>
         </tr>
       </TMPL_LOOP>
     </table>
</noscript>

</div> <!-- end of #map_canvas -->

</body>
</html>

That comment up top is the whole plot: this <noscript> chunk is what gets used when javascript is off, and it's there to show the info you'd normally get by hovering over or clicking markers on the dynamic map display. Then we shut </div> <!-- end of #map_canvas -->, </body>, </html> and the markup is done, periodt. πŸŽ€


✨ The perl ✨

The actual perl that spits out this web page? Has to look something like this:

use HTML::Template::Pluggable;
use HTML::Template::Plugin::Dot;
use Geo::Google::MapObject;
my $t = HTML::Template::Pluggable->new(filename => 'map.tmpl');
my $map = Geo::Google::MapObject->new(....);
$t->param(maps=>$map);
print $t->output;

Fr fr, that's genuinely all you need in a javascript-free environment. ✨ But once javascript is enabled, the client is gonna come asking for the exact same data in JSON format β€” and bestie, we are only supporting JSON, that's just the vibe. The code that services that request looks something like:

use Geo::Google::MapObject;
my $map = Geo::Google::MapObject->new(....);
print $map->json;

✨ The javascript ✨

Like the markup up there already hinted, we're expecting the javascript to live in a file called maps.js. And we expect it to look something like the following β€” buckle up, it's giving main character energy πŸ‘‘:

function mapInitialize() {

    /* Read the "link" element that tells us how to contact the server. See markup above. */
    var get_init_data = YAHOO.util.Dom.get('get_init_data').href;

    /* Build the AJAX callback object consisting of three parts.  */
    var callback =
    {

        /* Part I: Function called in the event of a successful call. */
        success: function(o) {

              /* Convert the JSON response into a javascript object.
                 This structure corresponds to the Geo::Google::MapObject in perl
                 as intermediated by its "json" method.
               */
              var data = YAHOO.lang.JSON.parse(o.responseText);

              if (GBrowserIsCompatible()) {

                      /* Build up the Map object which will replace #map_canvas in the above markup. */
                      var mapopt = {};
                      if (data.size) {
                              mapopt.size = new GSize(parseInt(data.size.width), parseInt(data.size.height));
                      }
                      var markers = data.markers;
                      var map = new GMap2(YAHOO.util.Dom.get("map_canvas"), mapopt);
                      var maptype = null;
                      if (data.maptype) {
                              maptype = o.argument[parseInt(data.maptype)];
                      }
                      var zoom = null;
                      if (data.zoom) {
                              zoom = parseInt(data.zoom);
                      }
                      var center = markers[0].location;
                      if (data.center) {
                              center = data.center;
                      }
                      map.setCenter(GLatLng.fromUrlValue(center), zoom, maptype);
                      map.setUIToDefault();

                      /* Now for each marker build and add a GMarker object */
                      for(var i = 0; i < markers.length; i++) {

                              var opt = {title: markers[i].title};
                              if (!markers[i].href) {
                                      opt.clickable = false;
                              }
                              if (markers[i].icon) {
                                      opt.icon = new GIcon(G_DEFAULT_ICON, markers[i].icon);
                                      if (markers[i].shadow) {
                                              opt.icon.shadow = markers[i].shadow;
                                      }
                              }
                              var mark = new GMarker(GLatLng.fromUrlValue(markers[i].location), opt);
                              if (markers[i].href) {
                                      mark.href = markers[i].href;
                                      GEvent.addListener(mark, "click", function(){window.location=this.href;});
                              }
                              map.addOverlay(mark);
                      }
              }
        },  /* End of Part I */

        /* Part II: Function called in the event of failure */
        failure: function(o) {alert(o.statusText);},

        /* Part III: Data that is passed to the success function.
           We are using this to map from numerical maptypes that Geo::Google::MapObject has passed us to the
           forms used in Google maps API.
         */
        argument: [G_NORMAL_MAP, G_SATELLITE_MAP, G_PHYSICAL_MAP, G_HYBRID_MAP]

    }; /* End of constructing the AJAX callback object. */

    /* This calls the server which should hopefully kick off callback.success with lots of lovely data. */
    var transaction = YAHOO.util.Connect.asyncRequest('GET', get_init_data, callback, null);

} /* end of mapInitialize function */

/* This will make sure that we call the above function at a good time. */
YAHOO.util.Event.onDOMReady(mapInitialize);

/* We still have to do the memory cleanup ourselves. */
YAHOO.util.Event.addListener(window, 'unload', 'GUnload');

🌈 Taking it further 🌈

There are so many variations you can do with this, it's honestly iconic. The Geo::Google::MapObject object will pass on all fields it does not recognize via the json function, and the javascript can use those however it wants. Same deal the other way: fields you add to the marker specification get passed along by the markers function and can be used in the template in whatever way required. πŸ¦‹

You can also make the markers draggable, and have the act of dragging a marker update data on the server. There's an example of where that's been done at <http://testmaps.periapt.co.uk>. The details of how this is done depend intrinsically on the implementation of the data storage on the server. But I'd suggest that in general the steps required to make the markers draggable might be as follows:

  1. Clean interface to the data storage. β€” I think this is a clear case where an ORM (<http://en.wikipedia.org/wiki/Object-relational_mapping>) offers some advantages. You could look at DBIx::Class or write your own interface using DBI.
  2. Derive a class from Geo::Google::MapObject. β€” Your derived class constructor should get the data you need from the data storage interface layer, use it to create a Geo::Google::MapObject and bless that into your derived class.
  3. Provide URLs that update the data underlying your map objects. β€” These will probably be POST methods and need not return anything other than an "OK" string. They should probably call the data storage interface layer directly to update the data. There is no point creating a map object merely to update the underlying data.
  4. Add link elements to your markup indicating which URL is needed to update the marker data.
<link href="/move_marker" id="move_marker"/>

An advantage of using these link elements is that you might have multiple maps to manage. By using the link elements to communicate with the client side, a lot more code and templates can be reused. πŸ’…

  1. Get the javascript side to read your new link elements.
var move_marker = YAHOO.util.Dom.get('move_marker').href;
  1. Add a "draggable" flag to the GMarker constructor
var opt = {title: markers[i].title, draggable: true};
  1. Make sure your GMarkers are labelled in a way that your server can understand β€” Just after the GMarker object, mark, has been created add
mark.label = markers[i].label;
  1. Make your GMarker object subscribe to the "dragend" event.
GEvent.addListener(mark,
                   "dragend",
                   function(latlng) {
                           var location = latlng.toUrlValue(12);
                           var postdata = "id="+this.label+"&location="+location;
                           YAHOO.util.Connect.asyncRequest('POST',
                                                           move_marker,
                                                           {
                                                              success: function(o){},
                                                              failure: function(o) {alert(o.statusText);},
                                                              argument: []
                                                           },
                                                           postdata);
                   });

πŸ’€ DIAGNOSTICS πŸ’€

okay bestie, these are the error messages this module throws when something's not giving. if you see one of these, it's telling you exactly what to fix, no cap:

  • "markers should be an ARRAY" β€” the markers param you handed the constructor has to be an ARRAY ref full of marker config data. that's the vibe. anything else? rejected.
  • "no API key" β€” bestie you literally cannot use this module without signing up for an API key (<http://code.google.com/apis/maps/signup.html>) and passing it in as the key parameter. no key, no map, periodt.
  • "zoom not a number: %s" β€” there must be a zoom parameter, and it's gotta be a number from 0 to 21. that's the whole range. respect it.
  • "maptype %s not recognized" β€” your maptype has to be one of roadmap, satellite, terrain, or hybrid. pick one, those are the only options, iykyk.
  • "no width"
  • "no height"
  • "width should be positive and no more than 640"
  • "height should be positive and no more than 640"
  • "cannot recognize size" β€” so the size parameter has to be either a string like "300x500" or a hash array like {width=>300,height=>500}. and yeah, both width and height have to land between 1 and 640 inclusive. no sneaking past 640, sorry πŸ’…
  • "cannot calculate autozoom without markers" β€” if you're passing an autozoom parameter to the constructor, you also have to pass at least one marker. the math needs something to look at, bestie.
  • "% not recognized as autozoom" β€” you passed an autozoom parameter to the constructor and it wasn't recognized as one. it's giving mystery value.
  • "location missing" β€” a marker spec was missing a location while the autozoom was being calculated. every marker needs somewhere to be.
  • "no location for %s" β€” literally every marker object must have a location. non-negotiable.

🌸 CONFIGURATION AND ENVIRONMENT 🌸

Geo::Google::MapObject needs absolutely no configuration files and no environment variables. zero setup drama. it just is. ✨


🧚 DEPENDENCIES 🧚

  • Templating framework β€” we're assuming you're rocking HTML::Template::Pluggable and HTML::Template::Plugin, though ngl other template frameworks may work too.
  • Google Maps API β€” you need one of these, and you can grab it from <http://code.google.com/apis/maps/signup.html>.
  • Javascript and AJAX β€” we're assuming you've got at least a degree of familiarity with javascript, AJAX, and client side programming. for the docs we assume YUI: <http://developer.yahoo.com/yui/>, but that framework choice is not mandated β€” do your own thing, main character.
  • Math::Trig β€” the autozoom algorithm (the optional one this module can use) depends on Math::Trig for its mathematical calculations. and since you could swap in alternative algorithms or just use no algorithm at all, the author thinks it might be better to keep this as a separate module. valid honestly.

πŸ™ˆ INCOMPATIBILITIES πŸ™ˆ

None reported. we're so clean. 🫢


😭 BUGS AND LIMITATIONS 😭

  • paths etc β€” right now there is no support for paths, polygons or viewports. tragic, i know.
  • version 3 β€” we are currently only supporting version 2 of the API. that's the one we ride with.
  • testing β€” we encode the title attributes of markers in the "json" function because that seems to be necessary. however the author has not yet managed to get a decent test script for this behaviour 😭. there are many other cases that should be tested too, though the most critical cases have been tested (probably).
  • character encoding β€” this module is only tested against UTF-8 web pages. and the author has no intention of changing that, because they cannot think of why anyone would consciously choose to encode web pages any other way. they are open to persuasion however. πŸ‘€

also, please report any bugs or feature requests to "bug-geo-google-mapobject@rt.cpan.org", or through the web interface at <http://rt.cpan.org>.


πŸ‘‘ ACKNOWLEDGEMENTS πŸ‘‘

shoutout to Andreas Koenig for pointing out that this module had an issue with Build.PL. king behaviour, we appreciate you. πŸ’–


🌈 AUTHOR 🌈

Nicholas Bamber "<nicholas@periapt.co.uk>"


Copyright (c) 2009, Nicholas Bamber "<nicholas@periapt.co.uk>". All rights reserved.

This module is free software; you can redistribute it and/or modify it under the same terms as Perl itself. See perlartistic.


πŸŽ€ DISCLAIMER OF WARRANTY πŸŽ€

bestie this next part is in ALL CAPS on purpose and you need to actually read it, it's the legal stuff and it's fr fr serious.

BECAUSE THIS SOFTWARE IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY FOR THE SOFTWARE, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE SOFTWARE "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE SOFTWARE IS WITH YOU. SHOULD THE SOFTWARE PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR, OR CORRECTION.

IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR REDISTRIBUTE THE SOFTWARE AS PERMITTED BY THE ABOVE LICENCE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE SOFTWARE (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE SOFTWARE TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.


and that's a wrap on the whole page, bestie!! πŸŽ‰ this lil doc drop came from perl v5.36.0 and was dated 2022-12-12, and the page itself is Geo::G...Object(3pm) from the User Contributed Perl Documentation. go build something iconic with it. πŸ’…βœ¨πŸ¦‹

πŸ“¦ where this page comes from