aboutsummaryrefslogtreecommitdiffstats
path: root/perllib
diff options
context:
space:
mode:
Diffstat (limited to 'perllib')
-rw-r--r--perllib/FixMyStreet/App/Controller/Report/New.pm1
-rwxr-xr-xperllib/FixMyStreet/App/Controller/Static.pm4
-rw-r--r--perllib/FixMyStreet/Cobrand/FixaMinGata.pm272
-rw-r--r--perllib/FixMyStreet/DB/Result/Body.pm2
-rw-r--r--perllib/FixMyStreet/Geocode/FixaMinGata.pm204
-rw-r--r--perllib/FixMyStreet/Map/OSM/MapQuest.pm4
-rw-r--r--perllib/Open311.pm25
-rw-r--r--perllib/Open311/GetUpdates.pm84
8 files changed, 583 insertions, 13 deletions
diff --git a/perllib/FixMyStreet/App/Controller/Report/New.pm b/perllib/FixMyStreet/App/Controller/Report/New.pm
index fcab4e49a..a419e9cc1 100644
--- a/perllib/FixMyStreet/App/Controller/Report/New.pm
+++ b/perllib/FixMyStreet/App/Controller/Report/New.pm
@@ -678,6 +678,7 @@ sub setup_categories_and_bodies : Private {
# put results onto stash for display
$c->stash->{bodies} = \%bodies;
$c->stash->{all_body_names} = [ map { $_->name } values %bodies ];
+ $c->stash->{all_body_urls} = [ map { $_->external_url } values %bodies ];
$c->stash->{bodies_to_list} = [ keys %bodies_to_list ];
$c->stash->{category_options} = \@category_options;
$c->stash->{category_extras} = \%category_extras;
diff --git a/perllib/FixMyStreet/App/Controller/Static.pm b/perllib/FixMyStreet/App/Controller/Static.pm
index 6cc22aede..40e2431ea 100755
--- a/perllib/FixMyStreet/App/Controller/Static.pm
+++ b/perllib/FixMyStreet/App/Controller/Static.pm
@@ -57,6 +57,10 @@ sub iphone : Global : Args(0) {
my ( $self, $c ) = @_;
}
+sub council : Global : Args(0) {
+ my ( $self, $c ) = @_;
+}
+
__PACKAGE__->meta->make_immutable;
1;
diff --git a/perllib/FixMyStreet/Cobrand/FixaMinGata.pm b/perllib/FixMyStreet/Cobrand/FixaMinGata.pm
new file mode 100644
index 000000000..f17f7716f
--- /dev/null
+++ b/perllib/FixMyStreet/Cobrand/FixaMinGata.pm
@@ -0,0 +1,272 @@
+package FixMyStreet::Cobrand::FixaMinGata;
+use base 'FixMyStreet::Cobrand::Default';
+
+use strict;
+use warnings;
+
+use Carp;
+use mySociety::MaPit;
+use FixMyStreet::Geocode::FixaMinGata;
+use DateTime;
+
+
+DateTime->DefaultLocale('sv_SE');
+
+sub site_title {
+ my ($self) = @_;
+ return 'Fixa Min Gata';
+}
+
+sub path_to_web_templates {
+ my $self = shift;
+ return [
+ FixMyStreet->path_to( 'templates/web', $self->moniker )->stringify,
+ FixMyStreet->path_to( 'templates/web/fixmystreet' )->stringify
+ ];
+}
+
+sub country {
+ return 'SE';
+}
+
+sub languages { [ 'en-gb,English,en_GB', 'sv,Swedish,sv_SE' ] }
+sub language_override { 'sv' }
+
+sub enter_postcode_text {
+ my ( $self ) = @_;
+ return _('Enter a nearby postcode, or street name and area');
+}
+
+# Is also adding language parameter
+sub disambiguate_location {
+ return {
+ lang => 'sv',
+ country => 'se', # Is this the right format? /Rikard
+ };
+}
+
+sub area_types {
+ [ 'KOM' ];
+}
+
+sub admin_base_url {
+ return 'http://www.fixamingata.se/admin/';
+}
+
+# If lat/lon are present in the URL, OpenLayers will use that to centre the map.
+# Need to specify a zoom to stop it defaulting to null/0.
+sub uri {
+ my ( $self, $uri ) = @_;
+
+ $uri->query_param( zoom => 3 )
+ if $uri->query_param('lat') && !$uri->query_param('zoom');
+
+ return $uri;
+}
+
+sub geocode_postcode {
+ my ( $self, $s ) = @_;
+ # Most people write Swedish postcodes like this:
+ #+ XXX XX, so let's remove the space
+ # Is this the right place to do this? //Rikard
+ # This is the right place! // Jonas
+ $s =~ s/\ //g; # Rikard, remove space in postcode
+ if ($s =~ /^\d{5}$/) {
+ my $location = mySociety::MaPit::call('postcode', $s);
+ if ($location->{error}) {
+ return {
+ error => $location->{code} =~ /^4/
+ ? _('That postcode was not recognised, sorry.')
+ : $location->{error}
+ };
+ }
+ return {
+ latitude => $location->{wgs84_lat},
+ longitude => $location->{wgs84_lon},
+ };
+ }
+ return {};
+}
+
+# Vad gör den här funktionen? Är "Sverige" rätt här?
+sub geocoded_string_check {
+ my ( $self, $s ) = @_;
+ return 1 if $s =~ /, Sverige/;
+ return 0;
+}
+
+sub find_closest {
+ my ( $self, $latitude, $longitude ) = @_;
+ return FixMyStreet::Geocode::OSM::closest_road_text( $self, $latitude, $longitude );
+}
+
+# Used by send-reports, calling find_closest, calling OSM geocoding
+sub guess_road_operator {
+ my ( $self, $inforef ) = @_;
+
+ my $highway = $inforef->{highway} || "unknown";
+ my $refs = $inforef->{ref} || "unknown";
+ return "Trafikverket"
+ if $highway eq "trunk" || $highway eq "primary";
+
+ for my $ref (split(/;/, $refs)) {
+ return "Trafikverket"
+ if $ref =~ m/E ?\d+/ || $ref =~ m/Fv\d+/i;
+ }
+ return '';
+}
+
+sub remove_redundant_councils {
+ my $self = shift;
+ my $all_councils = shift;
+
+ # Oslo is both a kommune and a fylke, we only want to show it once
+ # Jag tror inte detta är applicerbart på Sverige ;-) //Rikard
+ #delete $all_councils->{301} #
+ # if $all_councils->{3};
+}
+
+sub filter_all_council_ids_list {
+ my $self = shift;
+ my @all_councils_ids = @_;
+
+ # as above we only want to show Oslo once
+ # Rikard kommenterar ut detta.
+ # return grep { $_ != 301 } @all_councils_ids;
+ # Rikard:
+ return @all_councils_ids; # Är detta rätt? //Rikard
+}
+
+sub short_name {
+ my $self = shift;
+ my ($area, $info) = @_;
+
+ # Rikard kommenterar ut följande tills vidare...
+ #if ($area->{name} =~ /^(Os|Nes|V\xe5ler|Sande|B\xf8|Her\xf8y)$/) {
+ # my $parent = $info->{$area->{parent_area}}->{name};
+ # return URI::Escape::uri_escape_utf8("$area->{name}, $parent");
+ #}
+
+ my $name = $area->{name};
+ $name =~ s/ & / and /;
+ $name = URI::Escape::uri_escape_utf8($name);
+ $name =~ s/%20/+/g;
+ return $name;
+}
+
+# Vad ska vi göra för svenska förhållanden här??? //Rikard
+sub council_rss_alert_options {
+ my $self = shift;
+ my $all_councils = shift;
+ my $c = shift;
+
+ my ( @options, @reported_to_options, $fylke, $kommune );
+
+ foreach ( values %$all_councils ) {
+ if ( $_->{type} eq 'NKO' ) {
+ $kommune = $_;
+ }
+ else {
+ $fylke = $_;
+ }
+ }
+
+ if ( $fylke->{id} == 3 ) { # Oslo
+ my $short_name = $self->short_name($fylke, $all_councils);
+ ( my $id_name = $short_name ) =~ tr/+/_/;
+
+ push @options,
+ {
+ type => 'council',
+ id => sprintf( 'council:%s:%s', $fylke->{id}, $id_name ),
+ rss_text =>
+ sprintf( _('RSS feed of problems within %s'), $fylke->{name} ),
+ text => sprintf( _('Problems within %s'), $fylke->{name} ),
+ uri => $c->uri_for( '/rss/reports', $short_name ),
+ };
+ }
+ else {
+ my $short_kommune_name = $self->short_name($kommune, $all_councils);
+ ( my $id_kommune_name = $short_kommune_name ) =~ tr/+/_/;
+
+ my $short_fylke_name = $self->short_name($fylke, $all_councils);
+ ( my $id_fylke_name = $short_fylke_name ) =~ tr/+/_/;
+
+ push @options,
+ {
+ type => 'area',
+ id => sprintf( 'area:%s:%s', $kommune->{id}, $id_kommune_name ),
+ rss_text =>
+ sprintf( _('RSS feed of %s'), $kommune->{name} ),
+ text => $kommune->{name},
+ uri => $c->uri_for( '/rss/area', $short_kommune_name ),
+ },
+ {
+ type => 'area',
+ id => sprintf( 'area:%s:%s', $fylke->{id}, $id_fylke_name ),
+ rss_text =>
+ sprintf( _('RSS feed of %s'), $fylke->{name} ),
+ text => $fylke->{name},
+ uri => $c->uri_for( '/rss/area', $short_fylke_name ),
+ };
+
+ push @reported_to_options,
+ {
+ type => 'council',
+ id => sprintf( 'council:%s:%s', $kommune->{id}, $id_kommune_name ),
+ rss_text =>
+ sprintf( _('RSS feed of %s'), $kommune->{name} ),
+ text => $kommune->{name},
+ uri => $c->uri_for( '/rss/reports', $short_kommune_name ),
+ },
+ {
+ type => 'council',
+ id => sprintf( 'council:%s:%s', $fylke->{id}, $id_fylke_name ),
+ rss_text =>
+ sprintf( _('RSS feed of %s'), $fylke->{name} ),
+ text => $fylke->{name},
+ uri => $c->uri_for( '/rss/reports/', $short_fylke_name ),
+ };
+ }
+
+ return (
+ \@options, @reported_to_options
+ ? \@reported_to_options
+ : undef
+ );
+
+}
+
+# Vad ska vi göra för svenska förhållanden här??? //Rikard
+sub reports_council_check {
+ my ( $self, $c, $council ) = @_;
+
+ if ($council eq 'Oslo') {
+
+ # There are two Oslos (kommune and fylke), we only want one of them.
+ $c->stash->{council} = mySociety::MaPit::call('area', 3);
+ return 1;
+
+ } elsif ($council =~ /,/) {
+
+ # Some kommunes have the same name, use the fylke name to work out which.
+ my ($kommune, $fylke) = split /\s*,\s*/, $council;
+ my $area_types = $c->cobrand->area_types;
+ my $areas_k = mySociety::MaPit::call('areas', $kommune, type => $area_types);
+ my $areas_f = mySociety::MaPit::call('areas', $fylke, type => $area_types);
+ if (keys %$areas_f == 1) {
+ ($fylke) = values %$areas_f;
+ foreach (values %$areas_k) {
+ if ($_->{name} eq $kommune && $_->{parent_area} == $fylke->{id}) {
+ $c->stash->{council} = $_;
+ return 1;
+ }
+ }
+ }
+ # If we're here, we've been given a bad name.
+ $c->detach( 'redirect_index' );
+
+ }
+}
+
+1;
diff --git a/perllib/FixMyStreet/DB/Result/Body.pm b/perllib/FixMyStreet/DB/Result/Body.pm
index c2b0555fb..be4adeca9 100644
--- a/perllib/FixMyStreet/DB/Result/Body.pm
+++ b/perllib/FixMyStreet/DB/Result/Body.pm
@@ -42,6 +42,8 @@ __PACKAGE__->add_columns(
{ data_type => "boolean", default_value => \"false", is_nullable => 0 },
"deleted",
{ data_type => "boolean", default_value => \"false", is_nullable => 0 },
+ "external_url",
+ { data_type => "text", is_nullable => 0 },
);
__PACKAGE__->set_primary_key("id");
__PACKAGE__->has_many(
diff --git a/perllib/FixMyStreet/Geocode/FixaMinGata.pm b/perllib/FixMyStreet/Geocode/FixaMinGata.pm
new file mode 100644
index 000000000..2db25f504
--- /dev/null
+++ b/perllib/FixMyStreet/Geocode/FixaMinGata.pm
@@ -0,0 +1,204 @@
+#!/usr/bin/perl
+#
+# FixMyStreet:Geocode::FixaMinGata
+# OpenStreetmap forward and reverse geocoding for FixMyStreet.
+#
+# Copyright (c) 2011 Petter Reinholdtsen. Some rights reserved.
+# Email: pere@hungry.com
+
+# This module is a slightly derived version of OSM.pm.
+
+# As of January 2014, the FixaMinGata developers are considering to make further
+# changes related to OSM, so it's probably best to keep this module separate
+# from the OSM module for now.
+
+package FixMyStreet::Geocode::FixaMinGata;
+
+use warnings;
+use strict;
+use Data::Dumper;
+
+use Digest::MD5 qw(md5_hex);
+use Encode;
+use File::Slurp;
+use File::Path ();
+use LWP::Simple qw($ua);
+use Memcached;
+use XML::Simple;
+use mySociety::Locale;
+
+my $osmapibase = "http://www.openstreetmap.org/api/";
+my $nominatimbase = "http://nominatim.openstreetmap.org/";
+
+# string STRING CONTEXT
+# Looks up on Nominatim, and caches, a user-inputted location.
+# Returns array of (LAT, LON, ERROR), where ERROR is either undef, a string, or
+# an array of matches if there are more than one. The information in the query
+# may be used to disambiguate the location in cobranded versions of the site.
+sub string {
+ my ( $s, $c ) = @_;
+
+ my $params = $c->cobrand->disambiguate_location($s);
+
+ $s = FixMyStreet::Geocode::escape($s);
+ # $s .= '+' . $params->{town} if $params->{town} and $s !~ /$params->{town}/i;
+
+ my $url = "${nominatimbase}search?";
+ my %query_params = (
+ q => $s,
+ format => 'json',
+ addressdetails => 1,
+ limit => 20,
+ #'accept-language' => '',
+ email => 'info' . chr(64) . 'morus.se',
+ );
+ # $query_params{viewbox} = $params->{bounds}[1] . ',' . $params->{bounds}[2] . ',' . $params->{bounds}[3] . ',' . $params->{bounds}[0]
+ # if $params->{bounds};
+ $query_params{countrycodes} = $params->{country}
+ if $params->{country};
+ $url .= join('&', map { "$_=$query_params{$_}" } keys %query_params);
+
+ my $cache_dir = FixMyStreet->config('GEO_CACHE') . 'osm/';
+ my $cache_file = $cache_dir . md5_hex($url);
+ my $js;
+ if (-s $cache_file) {
+ $js = File::Slurp::read_file($cache_file);
+ } else {
+ $ua->timeout(15);
+ $js = LWP::Simple::get($url);
+ $js = encode_utf8($js) if utf8::is_utf8($js);
+ File::Path::mkpath($cache_dir);
+ File::Slurp::write_file($cache_file, $js) if $js;
+ }
+
+ if (!$js) {
+ return { error => _('Sorry, we could not find that location.') };
+ }
+
+ $js = JSON->new->utf8->allow_nonref->decode($js);
+
+ my ( %locations, $error, @valid_locations, $latitude, $longitude );
+ foreach (@$js) {
+ # These co-ordinates are output as query parameters in a URL, make sure they have a "."
+ next if $_->{class} eq "boundary";
+
+ my @s = split(/,/, $_->{display_name});
+
+ my $address = join(",", @s[0,1,2]);
+
+ $locations{$address} = [$_->{lat}, $_->{lon}];
+ }
+
+ my ($key) = keys %locations;
+
+ return { latitude => $locations{$key}[0], longitude => $locations{$key}[1] } if scalar keys %locations == 1;
+ return { error => _('Sorry, we could not find that location.') } if scalar keys %locations == 0;
+
+ foreach $key (keys %locations) {
+ ( $latitude, $longitude ) = ($locations{$key}[0], $locations{$key}[1]);
+ mySociety::Locale::in_gb_locale {
+ push (@$error, {
+ address => $key,
+ latitude => sprintf('%0.6f', $latitude),
+ longitude => sprintf('%0.6f', $longitude)
+ });
+ };
+ }
+
+ return { error => $error };
+}
+
+sub reverse_geocode {
+ my ($latitude, $longitude, $zoom) = @_;
+ my $url =
+ "${nominatimbase}reverse?format=xml&zoom=$zoom&lat=$latitude&lon=$longitude";
+ my $key = "OSM:reverse_geocode:$url";
+ my $result = Memcached::get($key);
+ unless ($result) {
+ my $j = LWP::Simple::get($url);
+ if ($j) {
+ Memcached::set($key, $j, 3600);
+ my $ref = XMLin($j);
+ return $ref;
+ } else {
+ print STDERR "No reply from $url\n";
+ }
+ return undef;
+ }
+ return XMLin($result);
+}
+
+sub _osmxml_to_hash {
+ my ($xml, $type) = @_;
+ my $ref = XMLin($xml);
+ my %tags;
+ if ('ARRAY' eq ref $ref->{$type}->{tag}) {
+ map { $tags{$_->{'k'}} = $_->{'v'} } @{$ref->{$type}->{tag}};
+ return \%tags;
+ } else {
+ return undef;
+ }
+}
+
+sub get_object_tags {
+ my ($type, $id) = @_;
+ my $url = "${osmapibase}0.6/$type/$id";
+ my $key = "OSM:get_object_tags:$url";
+ my $result = Memcached::get($key);
+ unless ($result) {
+ my $j = LWP::Simple::get($url);
+ if ($j) {
+ Memcached::set($key, $j, 3600);
+ return _osmxml_to_hash($j, $type);
+ } else {
+ print STDERR "No reply from $url\n";
+ }
+ return undef;
+ }
+ return _osmxml_to_hash($result, $type);
+}
+
+# A better alternative might be
+# http://www.geonames.org/maps/osm-reverse-geocoder.html#findNearbyStreetsOSM
+sub get_nearest_road_tags {
+ my ( $cobrand, $latitude, $longitude ) = @_;
+ my $inforef = reverse_geocode($latitude, $longitude, 16);
+ if (exists $inforef->{result}->{osm_type}
+ && 'way' eq $inforef->{result}->{osm_type}) {
+ my $osmtags = get_object_tags('way',
+ $inforef->{result}->{osm_id});
+ unless ( exists $osmtags->{operator} ) {
+ $osmtags->{operatorguess} = $cobrand->guess_road_operator( $osmtags );
+ }
+ return $osmtags;
+ }
+ return undef;
+}
+
+sub closest_road_text {
+ my ( $cobrand, $latitude, $longitude ) = @_;
+ my $str = '';
+ my $osmtags = get_nearest_road_tags( $cobrand, $latitude, $longitude );
+ if ($osmtags) {
+ my ($name, $ref) = ('','');
+ $name = $osmtags->{name} if exists $osmtags->{name};
+ $ref = " ($osmtags->{ref})" if exists $osmtags->{ref};
+ if ($name || $ref) {
+ $str .= _('The following information about the nearest road might be inaccurate or irrelevant, if the problem is close to several roads or close to a road without a name registered in OpenStreetMap.') . "\n\n";
+ $str .= sprintf(_("Nearest named road to the pin placed on the map (automatically generated using OpenStreetMap): %s%s"),
+ $name, $ref) . "\n\n";
+
+ if (my $operator = $osmtags->{operator}) {
+ $str .= sprintf(_("Road operator for this named road (from OpenStreetMap): %s"),
+ $operator) . "\n\n";
+ } elsif ($operator = $osmtags->{operatorguess}) {
+ $str .= sprintf(_("Road operator for this named road (derived from road reference number and type): %s"),
+ $operator) . "\n\n";
+ }
+ }
+ }
+ return $str;
+}
+
+1;
+
diff --git a/perllib/FixMyStreet/Map/OSM/MapQuest.pm b/perllib/FixMyStreet/Map/OSM/MapQuest.pm
index ff314a4da..a7f1b334e 100644
--- a/perllib/FixMyStreet/Map/OSM/MapQuest.pm
+++ b/perllib/FixMyStreet/Map/OSM/MapQuest.pm
@@ -15,6 +15,10 @@ sub map_type {
return 'OpenLayers.Layer.OSM.MapQuestOpen';
}
+sub map_template {
+ return 'mapquest-attribution';
+}
+
sub map_tiles {
my ( $self, %params ) = @_;
my ( $x, $y, $z ) = ( $params{x_tile}, $params{y_tile}, $params{zoom_act} );
diff --git a/perllib/Open311.pm b/perllib/Open311.pm
index 25119b867..c8289a442 100644
--- a/perllib/Open311.pm
+++ b/perllib/Open311.pm
@@ -172,19 +172,18 @@ sub _generate_service_request_description {
my $problem = shift;
my $extra = shift;
- my $description = <<EOT;
-detail: @{[$problem->detail()]}
-
-url: $extra->{url}
-
-Submitted via FixMyStreet
-EOT
-;
- if ($self->extended_description ne 'oxfordshire') {
- $description = <<EOT . $description;
-title: @{[$problem->title()]}
-
-EOT
+ my $description = "";
+ if ($problem->cobrand eq 'fixmystreet') {
+ $description .= "detail: " . $problem->detail . "\n\n";
+ $description .= "url: " . $extra->{url} . "\n\n";
+ $description .= "Submitted via FixMyStreet\n";
+ if ($self->extended_description ne 'oxfordshire') {
+ $description = "title: " . $problem->title . "\n\n$description";
+ }
+ } else {
+ $description .= $problem->title . "\n\n";
+ $description .= $problem->detail . "\n\n";
+ $description .= $extra->{url} . "\n";
}
return $description;
diff --git a/perllib/Open311/GetUpdates.pm b/perllib/Open311/GetUpdates.pm
new file mode 100644
index 000000000..db5d8ef35
--- /dev/null
+++ b/perllib/Open311/GetUpdates.pm
@@ -0,0 +1,84 @@
+package Open311::GetUpdates;
+
+use Moose;
+use Open311;
+use FixMyStreet::App;
+
+has council_list => ( is => 'ro' );
+has system_user => ( is => 'ro' );
+
+sub get_updates {
+ my $self = shift;
+
+ while ( my $council = $self->council_list->next ) {
+ my $open311 = Open311->new(
+ endpoint => $council->endpoint,
+ jurisdiction => $council->jurisdiction,
+ api_key => $council->api_key
+ );
+
+ my $area_id = $council->area_id;
+
+ my $council_details = mySociety::MaPit::call( 'area', $area_id );
+
+ my $reports = FixMyStreet::App->model('DB::Problem')->search(
+ {
+ council => { like => "\%$area_id\%" },
+ state => { 'IN', [qw/confirmed fixed/] },
+ -and => [
+ external_id => { '!=', undef },
+ external_id => { '!=', '' },
+ ],
+ }
+ );
+
+ my @report_ids = ();
+ while ( my $report = $reports->next ) {
+ push @report_ids, $report->external_id;
+ }
+
+ next unless @report_ids;
+
+ $self->update_reports( \@report_ids, $open311, $council_details );
+ }
+}
+
+sub update_reports {
+ my ( $self, $report_ids, $open311, $council_details ) = @_;
+
+ my $service_requests = $open311->get_service_requests( $report_ids );
+
+ my $requests;
+
+ # XML::Simple is a bit inconsistent in how it structures
+ # things depending on the number of children an element has :(
+ if ( ref $service_requests->{request} eq 'ARRAY' ) {
+ $requests = $service_requests->{request};
+ }
+ else {
+ $requests = [ $service_requests->{request} ];
+ }
+
+ for my $request (@$requests) {
+ # if it's a ref that means it's an empty element
+ # however, if there's no updated date then we can't
+ # tell if it's newer that what we have so we should skip it
+ next if ref $request->{updated_datetime} || ! exists $request->{updated_datetime};
+
+ my $request_id = $request->{service_request_id};
+
+ my $problem =
+ FixMyStreet::App->model('DB::Problem')
+ ->search( { external_id => $request_id, } );
+
+ if (my $p = $problem->first) {
+ my $cobrand = FixMyStreet::Cobrand->get_class_for_moniker($p->cobrand)->new();
+ $cobrand->set_lang_and_domain($p->lang, 1);
+ $p->update_from_open311_service_request( $request, $council_details, $self->system_user );
+ }
+ }
+
+ return 1;
+}
+
+1;