GetLatLngFromGoogleUsingAddress   B
last analyzed

Complexity

Total Complexity 49

Size/Duplication

Total Lines 330
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 6

Importance

Changes 0
Metric Value
wmc 49
lcom 1
cbo 6
dl 0
loc 330
rs 8.48
c 0
b 0
f 0

6 Methods

Rating   Name   Duplication   Size   Complexity  
C get_placemark_as_array() 0 55 14
B get_placemark() 0 16 9
C get_geocode_obj() 0 43 11
A json_decoder() 0 4 1
A json_encoder() 0 4 1
C google_2_ss() 0 36 13

How to fix   Complexity   

Complex Class

Complex classes like GetLatLngFromGoogleUsingAddress often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use GetLatLngFromGoogleUsingAddress, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
/**
4
 * Geocode an address-string to a set of coordinates using Google's free
5
 * geocoding services.
6
 *
7
 * see: http://code.google.com/apis/maps/documentation/geocoding/index.html
8
 *
9
 * CHECKS IF CURL / file_get_contents is available
10
 * Requirements: allow_url_fopen = on
11
 *
12
 * @author Ingo Schomme and Nicolaas Francken
13
 * @todo Implement CURL with fopen fallback
14
 * @todo Implement client-side selection when multiple results are found (through validation-errors and javasript)
15
 * @see http://code.google.com/apis/maps/documentation/services.html#Geocoding_Direct
16
 *
17
 * you can use
18
 * GetLatLngFromGoogleUsingAddress::get_placemark_as_array()
19
 */
20
21
class GetLatLngFromGoogleUsingAddress extends Object
22
{
23
24
    /**
25
     * For debugging.
26
     * needs to be static because we use static methods
27
     * in this class
28
     * @var Boolean for debugging
29
     */
30
    private static $debug = false;
31
32
    /**
33
     * location for API
34
     *
35
     * @var String
36
     */
37
    private static $geocode_url = "https://maps.googleapis.com/maps/api/geocode/json?address=%s";
38
39
    /**
40
     * Additional data to send to the Google Server
41
     *
42
     * @var array
43
     */
44
    private static $additional_params = [];
45
46
    /**
47
       * default user to first result that is returned.
48
       *
49
       * @var boolean
50
       */
51
    private static $default_to_first_result = true;
52
53
    /**
54
     *
55
     * tells you if CURL / file_get_contents is available
56
     * we recommend you set to true , unless it is not sure if CURL is available
57
     *
58
     * @var boolean
59
     */
60
    private static $server_side_available = true;
61
62
    /**
63
     * alternative to api key
64
     * @var string
65
     */
66
    private static $google_client_id = "";
67
68
    /**
69
     * alternative api key.
70
     *
71
     * This has been added so that you can set up IP restrictions and HTTP referrer
72
     * restrictions within the Google API Console (you can not have both at the same time).
73
     *
74
     * @var string
75
     */
76
    private static $alternative_google_map_api_key = "";
77
78
    /**
79
     * Get first placemark as flat array
80
     *
81
     * @param string $q
82
     * @param bool $tryAnyway
83
     * @param array $params
84
     *
85
     * @return array
86
     */
87
    public static function get_placemark_as_array($q, $tryAnyway = false, $params = [])
88
    {
89
        //debug?
90
        $debug = Config::inst()->get("GetLatLngFromGoogleUsingAddress", "debug");
91
        $q = trim($q);
92
        if ($q) {
93
            //check if we have searched for this before ...
94
            $result = null;
95
            $filter = array("SearchPhrase" => Convert::raw2sql($q));
96
            $searchRecord = DataObject::get_one(
97
                'GetLatLngFromGoogleUsingAddressSearchRecord',
98
                $filter,
99
                $cacheDataObjectGetOne = false
100
            );
101
            if ($searchRecord && $searchRecord->ResultArray) {
102
                if ($debug) {
103
                    debug::show("Results from GetLatLngFromGoogleUsingAddressSearchRecord");
104
                }
105
                //@ is important here!
106
                $result = @unserialize($searchRecord->ResultArray);
107
                //remove result if it can not be read
108
                if ($result === null) {
109
                    $searchRecord->ResultArray = "";
110
                    $searchRecord->write();
111
                }
112
                //return details if they seem correct ...
113
                elseif (isset($result["FullAddress"]) && isset($result["Longitude"]) && isset($result["Latitude"])) {
114
                    return $result;
115
                }
116
                $result = null;
117
            }
118
            if (!$result) {
119
                $result = self::get_placemark($q, $tryAnyway, $params);
120
                if ($debug) {
121
                    debug::show(print_r($result, 1));
122
                }
123
                if (is_object($result)) {
124
                    $resultArray = self::google_2_ss($result);
125
                    if ($debug) {
126
                        debug::show(print_r($resultArray, 1));
127
                    }
128
                    if (!$searchRecord) {
129
                        $searchRecord = GetLatLngFromGoogleUsingAddressSearchRecord::create($filter);
130
                    }
131
                    $searchRecord->ResultArray = serialize($resultArray);
132
                    $searchRecord->write();
133
                    return $resultArray;
134
                } else {
135
                    return array("FullAddress"=> "Could not find address");
136
                }
137
            }
138
        } else {
139
            return array("FullAddress"=> "No search term provided");
140
        }
141
    }
142
143
144
    /**
145
    * Get first placemark from google, or return false.
146
    *
147
    * @param string $q
148
    * @param bool $tryAnyway
149
    * @param array $params
150
    *
151
    * @return Object Single placemark | false
152
    */
153
    protected static function get_placemark($q, $tryAnyway = false, $params = [])
154
    {
155
        if (Config::inst()->get("GetLatLngFromGoogleUsingAddress", "server_side_available") || $tryAnyway) {
156
            $responseObj = self::get_geocode_obj($q, $params);
157
            if (Config::inst()->get("GetLatLngFromGoogleUsingAddress", "debug")) {
158
                debug::show(print_r($responseObj, 1));
159
            }
160
            if ($responseObj && $responseObj->status == 'OK' && isset($responseObj->results[0])) {
161
                //we just take the first address!
162
                if (Config::inst()->get("GetLatLngFromGoogleUsingAddress", "default_to_first_result") || count($responseObj->results) ==1) {
163
                    $result = $responseObj->results[0];
164
                    return $result;
165
                }
166
            }
167
        }
168
    }
169
170
171
    /**
172
        * Get geocode from google.
173
        *
174
        * @see http://code.google.com/apis/maps/documentation/services.html#Geocoding_Direct
175
        *
176
        * @param string $q Place name (e.g. 'Portland' or '30th Avenue, New York")
177
        * @param array $params any additional params for the cURL request.
178
        *
179
        * @return Object Multiple Placemarks and status code
0 ignored issues
show
Documentation introduced by
Should the return type not be false|array?

This check compares the return type specified in the @return annotation of a function or method doc comment with the types returned by the function and raises an issue if they mismatch.

Loading history...
180
        */
181
    protected static function get_geocode_obj($q, $params = [])
182
    {
183
        $debug = Config::inst()->get("GetLatLngFromGoogleUsingAddress", "debug");
184
        $q = trim($q);
185
        if ($debug) {
186
            debug::show(print_r($q, 1));
187
        }
188
        if (empty($q)) {
189
            return false;
190
        }
191
        $url = sprintf(Config::inst()->get("GetLatLngFromGoogleUsingAddress", "geocode_url"), urlencode($q));
192
        if ($clientID = Config::inst()->get("GetLatLngFromGoogleUsingAddress", "google_client_id")) {
193
            $url .= "&client=".$clientID;
194
        } elseif ($api = Config::inst()->get("GetLatLngFromGoogleUsingAddress", "alternative_google_map_api_key")) {
195
            $url .= "&key=".$api;
196
        } elseif ($api = Config::inst()->get("GoogleMap", "google_map_api_key")) {
197
            $url .= "&key=".$api;
198
        }
199
        $params = $params + Config::inst()->get("GetLatLngFromGoogleUsingAddress", "additional_params");
200
        foreach ($params as $key => $value) {
201
            $url .= $key.'='.urlencode($value);
202
        }
203
        if ($debug) {
204
            debug::show(print_r($url, 1));
205
        }
206
        $ch = curl_init($url);
207
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
208
        curl_setopt($ch, CURLOPT_VERBOSE, true);
209
        $responseString = curl_exec($ch);
210
        curl_close($ch);
211
        if (!$responseString) {
212
            $responseString = file_get_contents($url);
213
            if (!$responseString) {
214
                return false;
215
            }
216
        }
217
218
        if ($debug) {
219
            debug::show(print_r($responseString, 1));
220
        }
221
222
        return self::json_decoder($responseString);
223
    }
224
225
    /**
226
     *
227
     * @param String (JSON)
228
     * @param Boolean $assoc
229
     *
230
     * @return Array
231
     */
232
    private static function json_decoder($content, $assoc = false)
0 ignored issues
show
Unused Code introduced by
The parameter $assoc is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
233
    {
234
        return json_decode($content);
235
    }
236
237
    /**
238
     *
239
     * @param Array
240
     *
241
     * @return Array
0 ignored issues
show
Documentation introduced by
Should the return type not be string?

This check compares the return type specified in the @return annotation of a function or method doc comment with the types returned by the function and raises an issue if they mismatch.

Loading history...
242
     */
243
    private static function json_encoder($content)
0 ignored issues
show
Unused Code introduced by
This method is not used, and could be removed.
Loading history...
244
    {
245
        return json_encode($content);
246
    }
247
248
    /**
249
     *
250
     *
251
GOOGLE:
252
    street_address indicates a precise street address.
253
    route indicates a named route (such as "US 101").
254
    intersection indicates a major intersection, usually of two major roads.
255
    political indicates a political entity. Usually, this type indicates a polygon of some civil administration.
256
    country indicates the national political entity, and is typically the highest order type returned by the Geocoder.
257
    administrative_area_level_1 indicates a first-order civil entity below the country level. Within the United States, these administrative levels are states. Not all nations exhibit these administrative levels.
258
    administrative_area_level_2 indicates a second-order civil entity below the country level. Within the United States, these administrative levels are counties. Not all nations exhibit these administrative levels.
259
    administrative_area_level_3 indicates a third-order civil entity below the country level. This type indicates a minor civil division. Not all nations exhibit these administrative levels.
260
    colloquial_area indicates a commonly-used alternative name for the entity.
261
    locality indicates an incorporated city or town political entity.
262
    sublocality indicates an first-order civil entity below a locality
263
    neighborhood indicates a named neighborhood
264
    premise indicates a named location, usually a building or collection of buildings with a common name
265
    subpremise indicates a first-order entity below a named location, usually a singular building within a collection of buildings with a common name
266
    postal_code indicates a postal code as used to address postal mail within the country.
267
    natural_feature indicates a prominent natural feature.
268
    airport indicates an airport.
269
    park indicates a named park.
270
    point_of_interest indicates a named point of interest. Typically, these "POI"s are prominent local entities that don't easily fit in another category such as "Empire State Building" or "Statue of Liberty."
271
272
    post_box indicates a specific postal box.
273
    street_number indicates the precise street number.
274
    floor indicates the floor of a building address.
275
    room indicates the room of a building address.
276
277
SS:
278
    'Latitude' => 'Double(12,7)',
279
    'Longitude' => 'Double(12,7)',
280
    'PointString' => 'Text',
281
    'Address' => 'Text',
282
    'FullAddress' => 'Text',
283
    'CountryNameCode' => 'Varchar(3)',
284
    'AdministrativeAreaName' => 'Varchar(255)',
285
    'SubAdministrativeAreaName' => 'Varchar(255)',
286
    'LocalityName' => 'Varchar(255)',
287
    'PostalCodeNumber' => 'Varchar(30)',
288
    */
289
290
    protected static $google_2_ss_translation_array = array(
291
        "administrative_area_level_1" => "AdministrativeAreaName",
292
        //two into one
293
        "locality" => "SubAdministrativeAreaName",
294
        "administrative_area_level_2" => "SubAdministrativeAreaName",
295
        //two into one!
296
        "sublocality" => "LocalityName",
297
        "locality" => "LocalityName",
298
        //two into one!
299
        "street_address" => "FullAddress",
300
        "formatted_address" => "FullAddress",
301
        //key ones
302
        "lng" => "Longitude",
303
        "lat" => "Latitude",
304
        "country" => "CountryNameCode",
305
        "postal_code" => "PostalCodeNumber"
306
    );
307
308
    /**
309
     * Converts Google Response INTO Silverstripe Google Map Array
310
     * that can be saved into a GoogleMapLocationsObject
311
     * @param GoogleResponseObject (JSON)
312
     * @return Array
313
     */
314
    protected static function google_2_ss($responseObj)
315
    {
316
        //get address parts
317
        $outputArray = array(
318
            "Original"=> $responseObj,
319
            "FullAddress"=> "Could not find address"
320
        );
321
        $translationArray = Config::inst()->get("GetLatLngFromGoogleUsingAddress", "google_2_ss_translation_array");
322
        if (isset($responseObj->address_components) && is_array($responseObj->address_components)) {
323
            foreach ($responseObj->address_components as $addressItem) {
324
                if (
325
                    is_object($addressItem)
326
                    && isset($addressItem->types)
327
                    && is_array($addressItem->types)
328
                    && count($addressItem->types)
329
                    && isset($addressItem->short_name)
330
                ) {
331
                    if (isset($translationArray[$addressItem->types[0]])) {
332
                        $outputArray[$translationArray[$addressItem->types[0]]] = $addressItem->short_name;
333
                    } else {
334
                        $outputArray[$addressItem->types[0]] = $addressItem->short_name;
335
                    }
336
                }
337
            }
338
        }
339
        if (!empty($responseObj->geometry) && !empty($responseObj->geometry->location)) {
340
            $outputArray["Longitude"] = $responseObj->geometry->location->lng;
341
            $outputArray["Latitude"] = $responseObj->geometry->location->lat;
342
            $outputArray["Accuracy"] = $responseObj->geometry->location_type;
343
        }
344
        //get other data
345
        if (!empty($responseObj->formatted_address)) {
346
            $outputArray["FullAddress"] = $responseObj->formatted_address;
347
        }
348
        return $outputArray;
349
    }
350
}
351