Subject: | Garmin POILoader rejects Geo::Gpx files as invalid. Hack-fix attached. |
I've found that while EasyGPX can read Geo::Gpx's xml okay, Garmin's
POILoader rejects it as invalid. I tried running several online XML
Validators against both Geo::Gpx'x xml, and EasyGPX's xml, and they are
both invalid against the GPX 1.1 and 1.0 DTDs. I didn't investigate how
EasyGPX's files are invalid since POILoader accepts them, so I concentrated
on Geo::Gpx.
I have found that the basic problem is that Geo::Gpx is not following the
xml sequence specified in the GPX specification. I subclassed Geo::Gpx and
overrode the xml method with a decorator that re-parses the output string
and re-orders the <wpt> elements into the correct order. POILoader accepts
this file and the custom POIs now show up fine in my StreetPilot c330.
Here's the validator I used, and the GPX schema I found success with:
http://tools.decisionsoft.com/schemaValidate/
http://www.topografix.com/GPX/1/1/gpx.xsd
The basic problem within Geo::Gpx is that it's using a 'sort keys...'
ordering for processing the waypoint elements in the generic token handler.
It should probably have at least a specific handler for the wpt type to
preserve the order in the specification. I suspect the route and track
specifications are also ordered, but I didn't look at them.
Here's the guts of the change in my hack:
my @correct_order = (
'ele','time','magvar','geoidheight', 'name',
'cmt','desc','src','link','sym',
'type','fix','sat','hdop','vdio',
'pdop','ageofdgpsdata','dgpsid','extensions');
$result .=
sprintf "<wpt lat=\"%s\" lon=\"%s\">\n", $attr->{lat},$attr->{lon};
foreach my $key (@correct_order) {
$result .= "<$key>" . &Geo::Gpx::_enc( $wpt->{$key}) .
"</$key>\n" if defined $wpt->{$key};
}
$result .= "</wpt>\n";
Since the StreetPilot c330 doesn't support either routes or tracks, the hack
I have isn't suitable for public consumption. In fact, it's an ugly thing I
threw together to see if the ordering was the only problem POILoader had
with the xml... but it works for me for files which contain only waypoints.
The proper fix is to provide a better handler for the wpt elements.
I've attached my subclass hack for your amusement. I've also attached a
sample gpx file generated with it and which POILoader processes successfully.
I would suggest that Geo::Gpx would be much more valuable if the xml it
produces is compatible with POILoader and other xml-based tools without
having to be reparsed and rewritten by other applications like EasyGPS
first, which is a manual process on windows.
I'm presently running perl 5.8.8 on an old Red Hat 7.3 linux laptop.
Subject: | GUITruckstops.gpx |
Message body not shown because it is not plain text.
Subject: | Gpx.pm |
$Trucker::Gpx::VERSION = sprintf "%d.%03d", q$Revision: 1.1 $ =~ /(\d+)/g;
package Trucker::Gpx;
use base Geo::Gpx;
use HTML::Entities qw(encode_entities encode_entities_numeric);
use XML::Descent;
sub xml {
my $self = shift;
my $xml = $self->SUPER::xml(@_);
my ($header) = ($xml =~ /^(.+?)<metadata>/s);
my $result = "";
$result .= $header;
my $p = XML::Descent->new({
Input => \$xml,
Namespaces => 0
});
$p->on('gpx' => # gpx handler
sub {
my ($elem, $attr, $ctx) = @_;
$p->on('*' => # everything else handler
sub {
my ($elem, $attr, $ctx) = @_;
$result .= "<$elem>" . $p->xml . "</$elem>\n";
}
);
$p->on('wpt' => # waypoint handler within gpx handler
sub {
my ($elem, $attr, $ctx) = @_;
my $wpt = {};
$p->context($wpt);
$p->on( '*' =>
sub {
my ($elem, $attr, $ctx) = @_;
my $text = $p->text;
$ctx->{$elem}=$text;
$ctx->{attribs} = $attr;
}
);
$p->walk;
my @correct_order = (
'ele','time','magvar','geoidheight', 'name',
'cmt','desc','src','link','sym',
'type','fix','sat','hdop','vdio',
'pdop','ageofdgpsdata','dgpsid','extensions');
$result .= sprintf "<wpt lat=\"%s\" lon=\"%s\">\n", $attr->{lat},$attr->{lon};
foreach my $key (@correct_order) {
$result .= "<$key>" . &Geo::Gpx::_enc( $wpt->{$key}) . "</$key>\n" if defined $wpt->{$key};
}
$result .= "</wpt>\n";
}
); # end of waypoint handler
$p->walk;
}
);
$p->walk;
$result .= "</gpx>\n";
return $result;
}