001/*
002 * Copyright 2008-2012 Marc Wick, geonames.org
003 *
004 * Licensed under the Apache License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 *     http://www.apache.org/licenses/LICENSE-2.0
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 *
016 */
017package org.geonames;
018
019import java.io.BufferedReader;
020import java.io.IOException;
021import java.io.InputStream;
022import java.io.InputStreamReader;
023import java.lang.reflect.Field;
024import java.net.HttpURLConnection;
025import java.net.MalformedURLException;
026import java.net.Proxy;
027import java.net.URL;
028import java.net.URLConnection;
029import java.net.URLEncoder;
030import java.text.ParseException;
031import java.text.SimpleDateFormat;
032import java.util.ArrayList;
033import java.util.List;
034import java.util.TimeZone;
035import java.util.logging.Level;
036import java.util.logging.Logger;
037
038import org.jdom.Document;
039import org.jdom.Element;
040import org.jdom.JDOMException;
041import org.jdom.input.SAXBuilder;
042
043/**
044 * provides static methods to access the
045 * <a href="http://www.geonames.org/export/ws-overview.html">GeoNames web
046 * services</a>.
047 * <p>
048 * Note : values for some fields are only returned with sufficient {@link Style}
049 * . Accessing these fields (admin codes and admin names, elevation,population)
050 * will throw an {@link InsufficientStyleException} if the {@link Style} was not
051 * sufficient.
052 * 
053 * @author marc@geonames
054 * 
055 */
056public class WebService {
057
058        private static Logger logger = Logger.getLogger("org.geonames");
059
060        private static String USER_AGENT = "gnwsc/1.1.15";
061
062        private static boolean isAndroid = false;
063
064        private static String geoNamesServer = "http://api.geonames.org";
065
066        private static String geoNamesServerFailover = "http://api.geonames.org";
067
068        private static long timeOfLastFailureMainServer;
069
070        private static long averageConnectTime;
071
072        private static long averageSampleSize = 20;
073
074        private static Style defaultStyle = Style.MEDIUM;
075
076        private static int readTimeOut = 120000;
077
078        private static int connectTimeOut = 10000;
079
080        private static String DATEFMT = "yyyy-MM-dd HH:mm:ss";
081
082        private static Proxy proxy;
083
084        static {
085                USER_AGENT += " (";
086                String os = System.getProperty("os.name");
087                if (os != null) {
088                        USER_AGENT += os + ",";
089                }
090                String osVersion = System.getProperty("os.version");
091                if (osVersion != null) {
092                        USER_AGENT += osVersion;
093                }
094                USER_AGENT += ")";
095
096                // android version
097                try {
098                        Class aClass = Class.forName("android.os.Build");
099                        if (aClass != null) {
100                                isAndroid = true;
101                                Field[] fields = aClass.getFields();
102                                if (fields != null) {
103                                        for (Field field : fields) {
104                                                if ("MODEL".equalsIgnoreCase(field.getName())) {
105                                                        USER_AGENT += "(" + field.get(aClass) + ", ";
106                                                }
107                                        }
108                                }
109                                aClass = Class.forName("android.os.Build$VERSION");
110                                if (aClass != null) {
111                                        fields = aClass.getFields();
112                                        if (fields != null) {
113                                                for (Field field : fields) {
114                                                        if ("RELEASE".equalsIgnoreCase(field.getName())) {
115                                                                USER_AGENT += field.get(aClass);
116                                                        }
117                                                }
118                                        }
119                                }
120                                USER_AGENT += ")";
121                        }
122                } catch (Throwable t) {
123                }
124        }
125
126        /**
127         * user name to pass to commercial web services for authentication and
128         * authorization
129         */
130        private static String userName;
131
132        /**
133         * token to pass to as optional authentication parameter to the commercial web
134         * services.
135         */
136        private static String token;
137
138        /**
139         * adds the username stored in a static variable to the url. It also adds a
140         * token if one has been set with the static setter previously.
141         * 
142         * @param url
143         * @return url with the username appended
144         */
145        private static String addUserName(String url) {
146                if (userName != null) {
147                        url = url + "&username=" + userName;
148                }
149                if (token != null) {
150                        url = url + "&token=" + token;
151                }
152                return url;
153        }
154
155        /**
156         * adds the default style to the url. The default style can be set with the
157         * static setter. It is 'MEDIUM' if not set.
158         * 
159         * @param url
160         * @return url with the style parameter appended
161         */
162        private static String addDefaultStyle(String url) {
163                if (defaultStyle != Style.MEDIUM) {
164                        url = url + "&style=" + defaultStyle.name();
165                }
166                return url;
167        }
168
169        /**
170         * returns the currently active server. Normally this is the main server, if the
171         * main server recently failed then the failover server is returned. If the main
172         * server is not available we don't want to try with every request whether it is
173         * available again. We switch to the failover server and try from time to time
174         * whether the main server is again accessible.
175         * 
176         * @return
177         */
178        private static String getCurrentlyActiveServer() {
179                if (timeOfLastFailureMainServer == 0) {
180                        // no problems with main server
181                        return geoNamesServer;
182                }
183                // we had problems with main server
184                if (System.currentTimeMillis() - timeOfLastFailureMainServer > 1000l * 60l * 10l) {
185                        // but is was some time ago and we switch back to the main server to
186                        // retry. The problem may have been solved in the mean time.
187                        timeOfLastFailureMainServer = 0;
188                        return geoNamesServer;
189                }
190                if (System.currentTimeMillis() < timeOfLastFailureMainServer) {
191                        throw new Error("time of last failure cannot be in future.");
192                }
193                // the problems have been very recent and we continue with failover
194                // server
195                if (geoNamesServerFailover != null) {
196                        return geoNamesServerFailover;
197                }
198                return geoNamesServer;
199        }
200
201        /**
202         * @return the isAndroid
203         */
204        public static boolean isAndroid() {
205                return isAndroid;
206        }
207
208        /**
209         * opens the connection to the url and sets the user agent. In case of an
210         * IOException it checks whether a failover server is set and connects to the
211         * failover server if it has been defined and if it is different from the normal
212         * server.
213         * 
214         * @param url the url to connect to
215         * @return returns the inputstream for the connection
216         * @throws IOException
217         */
218        private static InputStream connect(String url) throws IOException {
219                int status = 0;
220                String currentlyActiveServer = getCurrentlyActiveServer();
221                try {
222                        long begin = System.currentTimeMillis();
223                        HttpURLConnection httpConnection = null;
224                        if (proxy == null) {
225                                httpConnection = (HttpURLConnection) new URL(currentlyActiveServer + url).openConnection();
226                        } else {
227                                httpConnection = (HttpURLConnection) new URL(currentlyActiveServer + url).openConnection(proxy);
228                        }
229                        httpConnection.setConnectTimeout(connectTimeOut);
230                        httpConnection.setReadTimeout(readTimeOut);
231                        httpConnection.setRequestProperty("User-Agent", USER_AGENT);
232                        InputStream in = httpConnection.getInputStream();
233                        status = httpConnection.getResponseCode();
234
235                        if (status == 200) {
236                                long elapsedTime = System.currentTimeMillis() - begin;
237                                averageConnectTime = (averageConnectTime * (averageSampleSize - 1) + elapsedTime) / averageSampleSize;
238                                // if the average elapsed time is too long we switch server
239                                if (geoNamesServerFailover != null && averageConnectTime > 5000
240                                                && !currentlyActiveServer.equals(geoNamesServerFailover)) {
241                                        timeOfLastFailureMainServer = System.currentTimeMillis();
242                                }
243                                return in;
244                        }
245                } catch (IOException e) {
246                        return tryFailoverServer(url, currentlyActiveServer, 0, e);
247                }
248                // we only get here if we had a statuscode <> 200
249                IOException ioException = new IOException("status code " + status + " for " + url);
250                return tryFailoverServer(url, currentlyActiveServer, status, ioException);
251        }
252
253        private static synchronized InputStream tryFailoverServer(String url, String currentlyActiveServer, int status,
254                        IOException e) throws MalformedURLException, IOException {
255                // we cannot reach the server
256                logger.log(Level.WARNING, "problems connecting to geonames server " + currentlyActiveServer, e);
257                // is a failover server defined?
258                if (geoNamesServerFailover == null
259                                // is it different from the one we are using?
260                                || currentlyActiveServer.equals(geoNamesServerFailover)) {
261                        if (currentlyActiveServer.equals(geoNamesServerFailover)) {
262                                // failover server is not accessible, we throw exception
263                                // and switch back to main server.
264                                timeOfLastFailureMainServer = 0;
265                        }
266                        throw e;
267                }
268                timeOfLastFailureMainServer = System.currentTimeMillis();
269                logger.info("trying to connect to failover server " + geoNamesServerFailover);
270                // try failover server
271                URLConnection conn = null;
272                if (proxy == null) {
273                        conn = new URL(geoNamesServerFailover + url).openConnection();
274                } else {
275                        conn = new URL(geoNamesServerFailover + url).openConnection(proxy);
276                }
277
278                String userAgent = USER_AGENT + " failover from " + geoNamesServer;
279                if (status != 0) {
280                        userAgent += " " + status;
281                }
282                conn.setRequestProperty("User-Agent", userAgent);
283                InputStream in = conn.getInputStream();
284                return in;
285        }
286
287        private static Element connectAndParse(String url) throws GeoNamesException, IOException, JDOMException {
288                SAXBuilder parser = new SAXBuilder();
289                Document doc = parser.build(connect(url));
290                try {
291                        Element root = rootAndCheckException(doc);
292                        return root;
293                } catch (GeoNamesException geoNamesException) {
294                        if (geoNamesException.getExceptionCode() == 13 || (geoNamesException.getMessage() != null
295                                        && geoNamesException.getMessage().indexOf("canceling statement due to statement timeout") > -1)) {
296                                String currentlyActiveServer = getCurrentlyActiveServer();
297                                if (geoNamesServerFailover != null && !currentlyActiveServer.equals(geoNamesServerFailover)) {
298                                        timeOfLastFailureMainServer = System.currentTimeMillis();
299                                        doc = parser.build(connect(url));
300                                        Element root = rootAndCheckException(doc);
301                                        return root;
302                                }
303                        }
304                        throw geoNamesException;
305                }
306        }
307
308        private static Element rootAndCheckException(Document doc) throws GeoNamesException {
309                Element root = doc.getRootElement();
310                checkException(root);
311                return root;
312        }
313
314        private static void checkException(Element root) throws GeoNamesException {
315                Element message = root.getChild("status");
316                if (message != null) {
317                        int code = 0;
318                        try {
319                                code = Integer.parseInt(message.getAttributeValue("value"));
320                        } catch (NumberFormatException numberFormatException) {
321                        }
322                        throw new GeoNamesException(code, message.getAttributeValue("message"));
323                }
324        }
325
326        /**
327         * check whether we can parse an exception and throw it if we can
328         * 
329         * if the line starts with ERR: then we have a geonames exception.
330         * 
331         * @param line
332         * @throws GeoNamesException
333         */
334        private static void checkException(String line) throws GeoNamesException {
335                if (line.startsWith("ERR:")) {
336                        String[] terms = line.split(":");
337                        if (terms.length == 3) {
338                                throw new GeoNamesException(Integer.parseInt(terms[1]), terms[2]);
339                        }
340                        throw new GeoNamesException("unhandled exception");
341                }
342        }
343
344        private static Toponym getToponymFromElement(Element toponymElement) {
345                Toponym toponym = new Toponym();
346
347                toponym.setName(toponymElement.getChildText("name"));
348                toponym.setAlternateNames(toponymElement.getChildText("alternateNames"));
349                toponym.setLatitude(Double.parseDouble(toponymElement.getChildText("lat")));
350                toponym.setLongitude(Double.parseDouble(toponymElement.getChildText("lng")));
351
352                String geonameId = toponymElement.getChildText("geonameId");
353                if (geonameId != null) {
354                        toponym.setGeoNameId(Integer.parseInt(geonameId));
355                }
356
357                toponym.setContinentCode(toponymElement.getChildText("continentCode"));
358                toponym.setCountryCode(toponymElement.getChildText("countryCode"));
359                toponym.setCountryName(toponymElement.getChildText("countryName"));
360
361                toponym.setFeatureClass(FeatureClass.fromValue(toponymElement.getChildText("fcl")));
362                toponym.setFeatureCode(toponymElement.getChildText("fcode"));
363
364                toponym.setFeatureClassName(toponymElement.getChildText("fclName"));
365                toponym.setFeatureCodeName(toponymElement.getChildText("fCodeName"));
366
367                String population = toponymElement.getChildText("population");
368                if (population != null && !"".equals(population)) {
369                        toponym.setPopulation(Long.parseLong(population));
370                }
371                String elevation = toponymElement.getChildText("elevation");
372                if (elevation != null && !"".equals(elevation)) {
373                        toponym.setElevation(Integer.parseInt(elevation));
374                }
375
376                toponym.setAdminCode1(toponymElement.getChildText("adminCode1"));
377                toponym.setAdminName1(toponymElement.getChildText("adminName1"));
378                toponym.setAdminCode2(toponymElement.getChildText("adminCode2"));
379                toponym.setAdminName2(toponymElement.getChildText("adminName2"));
380                toponym.setAdminCode3(toponymElement.getChildText("adminCode3"));
381                toponym.setAdminName3(toponymElement.getChildText("adminName3"));
382                toponym.setAdminCode4(toponymElement.getChildText("adminCode4"));
383                toponym.setAdminName4(toponymElement.getChildText("adminName4"));
384                toponym.setAdminCode5(toponymElement.getChildText("adminCode5"));
385                toponym.setAdminName5(toponymElement.getChildText("adminName5"));
386
387                Element timezoneElement = toponymElement.getChild("timezone");
388                if (timezoneElement != null) {
389                        Timezone timezone = new Timezone();
390                        timezone.setTimezoneId(timezoneElement.getValue());
391                        timezone.setDstOffset(Double.parseDouble(timezoneElement.getAttributeValue("dstOffset")));
392                        timezone.setGmtOffset(Double.parseDouble(timezoneElement.getAttributeValue("gmtOffset")));
393                        toponym.setTimezone(timezone);
394                }
395
396                Element bboxElement = toponymElement.getChild("bbox");
397                if (bboxElement != null) {
398                        BoundingBox boundingBox = new BoundingBox(Double.parseDouble(bboxElement.getChildText("west")),
399                                        Double.parseDouble(bboxElement.getChildText("east")),
400                                        Double.parseDouble(bboxElement.getChildText("south")),
401                                        Double.parseDouble(bboxElement.getChildText("north")));
402                        toponym.setBoundingBox(boundingBox);
403                }
404
405                return toponym;
406        }
407
408        private static WikipediaArticle getWikipediaArticleFromElement(Element wikipediaArticleElement) {
409                WikipediaArticle wikipediaArticle = new WikipediaArticle();
410                wikipediaArticle.setLanguage(wikipediaArticleElement.getChildText("lang"));
411                wikipediaArticle.setTitle(wikipediaArticleElement.getChildText("title"));
412                wikipediaArticle.setSummary(wikipediaArticleElement.getChildText("summary"));
413                wikipediaArticle.setFeature(wikipediaArticleElement.getChildText("feature"));
414                wikipediaArticle.setWikipediaUrl(wikipediaArticleElement.getChildText("wikipediaUrl"));
415                wikipediaArticle.setThumbnailImg(wikipediaArticleElement.getChildText("thumbnailImg"));
416
417                wikipediaArticle.setLatitude(Double.parseDouble(wikipediaArticleElement.getChildText("lat")));
418                wikipediaArticle.setLongitude(Double.parseDouble(wikipediaArticleElement.getChildText("lng")));
419
420                wikipediaArticle.setRank(Integer.parseInt(wikipediaArticleElement.getChildText("rank")));
421
422                String population = wikipediaArticleElement.getChildText("population");
423                if (population != null && !"".equals(population)) {
424                        wikipediaArticle.setPopulation(Integer.parseInt(population));
425                }
426
427                String elevation = wikipediaArticleElement.getChildText("elevation");
428                if (elevation != null && !"".equals(elevation)) {
429                        wikipediaArticle.setElevation(Integer.parseInt(elevation));
430                }
431                return wikipediaArticle;
432        }
433
434        private static TimeZone utc = TimeZone.getTimeZone("UTC");
435
436        private static WeatherObservation getWeatherObservationFromElement(Element weatherObservationElement)
437                        throws ParseException {
438                WeatherObservation weatherObservation = new WeatherObservation();
439                weatherObservation.setObservation(weatherObservationElement.getChildText("observation"));
440                SimpleDateFormat df = new SimpleDateFormat(DATEFMT);
441                df.setTimeZone(utc);
442                weatherObservation.setObservationTime(df.parse(weatherObservationElement.getChildText("observationTime")));
443                weatherObservation.setStationName(weatherObservationElement.getChildText("stationName"));
444                weatherObservation.setIcaoCode(weatherObservationElement.getChildText("ICAO"));
445                weatherObservation.setCountryCode(weatherObservationElement.getChildText("countryCode"));
446                String elevation = weatherObservationElement.getChildText("elevation");
447                if (elevation != null && !"".equals(elevation)) {
448                        weatherObservation.setElevation(Integer.parseInt(elevation));
449                }
450                weatherObservation.setLatitude(Double.parseDouble(weatherObservationElement.getChildText("lat")));
451                weatherObservation.setLongitude(Double.parseDouble(weatherObservationElement.getChildText("lng")));
452                String temperature = weatherObservationElement.getChildText("temperature");
453                if (temperature != null && !"".equals(temperature)) {
454                        weatherObservation.setTemperature(Double.parseDouble(temperature));
455                }
456                String dewPoint = weatherObservationElement.getChildText("dewPoint");
457                if (dewPoint != null && !"".equals(dewPoint)) {
458                        weatherObservation.setDewPoint(Double.parseDouble(dewPoint));
459                }
460                String humidity = weatherObservationElement.getChildText("humidity");
461                if (humidity != null && !"".equals(humidity)) {
462                        weatherObservation.setHumidity(Double.parseDouble(humidity));
463                }
464                weatherObservation.setClouds(weatherObservationElement.getChildText("clouds"));
465                weatherObservation.setWeatherCondition(weatherObservationElement.getChildText("weatherCondition"));
466                weatherObservation.setWindSpeed(weatherObservationElement.getChildText("windSpeed"));
467                return weatherObservation;
468
469        }
470
471        /**
472         * returns a list of postal codes for the given parameters. This method is for
473         * convenience.
474         * 
475         * @param postalCode
476         * @param placeName
477         * @param countryCode
478         * @return
479         * @throws Exception
480         */
481        public static List<PostalCode> postalCodeSearch(String postalCode, String placeName, String countryCode)
482                        throws Exception {
483                PostalCodeSearchCriteria postalCodeSearchCriteria = new PostalCodeSearchCriteria();
484                postalCodeSearchCriteria.setPostalCode(postalCode);
485                postalCodeSearchCriteria.setPlaceName(placeName);
486                postalCodeSearchCriteria.setCountryCode(countryCode);
487                return postalCodeSearch(postalCodeSearchCriteria);
488        }
489
490        /**
491         * returns a list of postal codes for the given search criteria matching a full
492         * text search on the GeoNames postal codes database.
493         * 
494         * @param postalCodeSearchCriteria
495         * @return
496         * @throws Exception
497         */
498        public static List<PostalCode> postalCodeSearch(PostalCodeSearchCriteria postalCodeSearchCriteria)
499                        throws Exception {
500                List<PostalCode> postalCodes = new ArrayList<PostalCode>();
501
502                String url = "/postalCodeSearch?";
503                if (postalCodeSearchCriteria.getPostalCode() != null) {
504                        url = url + "postalcode=" + URLEncoder.encode(postalCodeSearchCriteria.getPostalCode(), "UTF8");
505                }
506                if (postalCodeSearchCriteria.getPlaceName() != null) {
507                        if (!url.endsWith("&")) {
508                                url = url + "&";
509                        }
510                        url = url + "placename=" + URLEncoder.encode(postalCodeSearchCriteria.getPlaceName(), "UTF8");
511                }
512                if (postalCodeSearchCriteria.getAdminCode1() != null) {
513                        url = url + "&adminCode1=" + URLEncoder.encode(postalCodeSearchCriteria.getAdminCode1(), "UTF8");
514                }
515
516                if (postalCodeSearchCriteria.getCountryCode() != null) {
517                        if (!url.endsWith("&")) {
518                                url = url + "&";
519                        }
520                        url = url + "country=" + postalCodeSearchCriteria.getCountryCode();
521                }
522                if (postalCodeSearchCriteria.getCountryBias() != null) {
523                        if (!url.endsWith("&")) {
524                                url = url + "&";
525                        }
526                        url = url + "countryBias=" + postalCodeSearchCriteria.getCountryBias();
527                }
528                if (postalCodeSearchCriteria.getMaxRows() > 0) {
529                        url = url + "&maxRows=" + postalCodeSearchCriteria.getMaxRows();
530                }
531                if (postalCodeSearchCriteria.getStartRow() > 0) {
532                        url = url + "&startRow=" + postalCodeSearchCriteria.getStartRow();
533                }
534                if (postalCodeSearchCriteria.isOROperator()) {
535                        url = url + "&operator=OR";
536                }
537                if (postalCodeSearchCriteria.isReduced() != null) {
538                        url = url + "&isReduced=" + postalCodeSearchCriteria.isReduced().toString();
539                }
540                if (postalCodeSearchCriteria.getBoundingBox() != null) {
541                        url = url + "&east=" + postalCodeSearchCriteria.getBoundingBox().getEast();
542                        url = url + "&west=" + postalCodeSearchCriteria.getBoundingBox().getWest();
543                        url = url + "&north=" + postalCodeSearchCriteria.getBoundingBox().getNorth();
544                        url = url + "&south=" + postalCodeSearchCriteria.getBoundingBox().getSouth();
545                }
546
547                url = addUserName(url);
548
549                Element root = connectAndParse(url);
550                for (Object obj : root.getChildren("code")) {
551                        Element codeElement = (Element) obj;
552                        PostalCode code = getPostalCodeFromElement(codeElement);
553                        postalCodes.add(code);
554                }
555
556                return postalCodes;
557        }
558
559        /**
560         * returns a list of postal codes
561         * 
562         * @param postalCodeSearchCriteria
563         * @return
564         * @throws Exception
565         */
566        public static List<PostalCode> findNearbyPostalCodes(PostalCodeSearchCriteria postalCodeSearchCriteria)
567                        throws Exception {
568
569                List<PostalCode> postalCodes = new ArrayList<PostalCode>();
570
571                String url = "/findNearbyPostalCodes?";
572                if (postalCodeSearchCriteria.getPostalCode() != null) {
573                        url = url + "&postalcode=" + URLEncoder.encode(postalCodeSearchCriteria.getPostalCode(), "UTF8");
574                }
575                if (postalCodeSearchCriteria.getPlaceName() != null) {
576                        url = url + "&placename=" + URLEncoder.encode(postalCodeSearchCriteria.getPlaceName(), "UTF8");
577                }
578                if (postalCodeSearchCriteria.getCountryCode() != null) {
579                        url = url + "&country=" + postalCodeSearchCriteria.getCountryCode();
580                }
581
582                if (postalCodeSearchCriteria.getLatitude() != null) {
583                        url = url + "&lat=" + postalCodeSearchCriteria.getLatitude();
584                }
585                if (postalCodeSearchCriteria.getLongitude() != null) {
586                        url = url + "&lng=" + postalCodeSearchCriteria.getLongitude();
587                }
588                if (postalCodeSearchCriteria.getStyle() != null) {
589                        url = url + "&style=" + postalCodeSearchCriteria.getStyle();
590                }
591                if (postalCodeSearchCriteria.getMaxRows() > 0) {
592                        url = url + "&maxRows=" + postalCodeSearchCriteria.getMaxRows();
593                }
594
595                if (postalCodeSearchCriteria.getRadius() > 0) {
596                        url = url + "&radius=" + postalCodeSearchCriteria.getRadius();
597                }
598                url = addUserName(url);
599
600                Element root = connectAndParse(url);
601                for (Object obj : root.getChildren("code")) {
602                        Element codeElement = (Element) obj;
603                        PostalCode code = getPostalCodeFromElement(codeElement);
604                        if (codeElement.getChildText("distance") != null) {
605                                code.setDistance(Double.parseDouble(codeElement.getChildText("distance")));
606                        }
607                        postalCodes.add(code);
608                }
609
610                return postalCodes;
611        }
612
613        private static PostalCode getPostalCodeFromElement(Element codeElement) throws ParseException {
614                PostalCode code = new PostalCode();
615                code.setPostalCode(codeElement.getChildText("postalcode"));
616                code.setPlaceName(codeElement.getChildText("name"));
617                code.setCountryCode(codeElement.getChildText("countryCode"));
618
619                code.setLatitude(Double.parseDouble(codeElement.getChildText("lat")));
620                code.setLongitude(Double.parseDouble(codeElement.getChildText("lng")));
621
622                code.setAdminName1(codeElement.getChildText("adminName1"));
623                code.setAdminCode1(codeElement.getChildText("adminCode1"));
624                code.setAdminName2(codeElement.getChildText("adminName2"));
625                code.setAdminCode2(codeElement.getChildText("adminCode2"));
626                code.setAdminName3(codeElement.getChildText("adminName3"));
627                code.setAdminCode3(codeElement.getChildText("adminCode3"));
628                return code;
629        }
630
631        /**
632         * convenience method for {@link #findNearbyPlaceName(double,double,double,int)}
633         * 
634         * @param latitude
635         * @param longitude
636         * @return
637         * @throws IOException
638         * @throws Exception
639         */
640        public static List<Toponym> findNearbyPlaceName(double latitude, double longitude) throws IOException, Exception {
641                return findNearbyPlaceName(latitude, longitude, 0, 0);
642        }
643
644        public static List<Toponym> findNearbyPlaceName(double latitude, double longitude, double radius, int maxRows)
645                        throws IOException, Exception {
646                List<Toponym> places = new ArrayList<Toponym>();
647
648                String url = "/findNearbyPlaceName?";
649
650                url = url + "&lat=" + latitude;
651                url = url + "&lng=" + longitude;
652                if (radius > 0) {
653                        url = url + "&radius=" + radius;
654                }
655                if (maxRows > 0) {
656                        url = url + "&maxRows=" + maxRows;
657                }
658                url = addUserName(url);
659                url = addDefaultStyle(url);
660
661                Element root = connectAndParse(url);
662                for (Object obj : root.getChildren("geoname")) {
663                        Element toponymElement = (Element) obj;
664                        Toponym toponym = getToponymFromElement(toponymElement);
665                        places.add(toponym);
666                }
667
668                return places;
669        }
670
671        public static List<Toponym> findNearby(double latitude, double longitude, FeatureClass featureClass,
672                        String[] featureCodes) throws IOException, Exception {
673                return findNearby(latitude, longitude, 0, featureClass, featureCodes, null, 0);
674        }
675
676        /* Overload function to allow backward compatibility */
677        /**
678         * Based on the following inforamtion: Webservice Type : REST
679         * api.geonames.org/findNearbyWikipedia? Parameters : lang : language code
680         * (around 240 languages) (default = en) lat,lng, radius (in km), maxRows
681         * (default = 10) featureClass featureCode Example:
682         * http://api.geonames.org/findNearby?lat=47.3&lng=9
683         * 
684         * @param: latitude
685         * @param: longitude
686         * @param: radius
687         * @param: feature Class
688         * @param: feature Codes
689         * @param: language
690         * @param: maxRows
691         * @return: list of wikipedia articles
692         * @throws: Exception
693         */
694        public static List<Toponym> findNearby(double latitude, double longitude, double radius, FeatureClass featureClass,
695                        String[] featureCodes, String language, int maxRows) throws IOException, Exception {
696                List<Toponym> places = new ArrayList<Toponym>();
697
698                String url = "/findNearby?";
699
700                url += "&lat=" + latitude;
701                url += "&lng=" + longitude;
702                if (radius > 0) {
703                        url = url + "&radius=" + radius;
704                }
705                if (maxRows > 0) {
706                        url = url + "&maxRows=" + maxRows;
707                }
708
709                if (language != null) {
710                        url = url + "&lang=" + language;
711                }
712
713                if (featureClass != null) {
714                        url += "&featureClass=" + featureClass;
715                }
716                if (featureCodes != null && featureCodes.length > 0) {
717                        for (String featureCode : featureCodes) {
718                                url += "&featureCode=" + featureCode;
719                        }
720                }
721
722                url = addUserName(url);
723                url = addDefaultStyle(url);
724
725                Element root = connectAndParse(url);
726                for (Object obj : root.getChildren("geoname")) {
727                        Element toponymElement = (Element) obj;
728                        Toponym toponym = getToponymFromElement(toponymElement);
729                        places.add(toponym);
730                }
731
732                return places;
733        }
734
735        /**
736         * 
737         * @param geoNameId
738         * @param language  - optional
739         * @param style     - optional
740         * @return the toponym for the geoNameId
741         * @throws IOException
742         * @throws Exception
743         */
744        public static Toponym get(int geoNameId, String language, String style) throws IOException, Exception {
745                String url = "/get?";
746
747                url += "geonameId=" + geoNameId;
748
749                if (language != null) {
750                        url = url + "&lang=" + language;
751                }
752
753                if (style != null) {
754                        url = url + "&style=" + style;
755                } else {
756                        url = addDefaultStyle(url);
757                }
758                url = addUserName(url);
759
760                Element root = connectAndParse(url);
761                Toponym toponym = getToponymFromElement(root);
762                return toponym;
763        }
764
765        /**
766         * for US only
767         * 
768         * @param latitude
769         * @param longitude
770         * @return
771         * @throws IOException
772         * @throws Exception
773         */
774        public static Address findNearestAddress(double latitude, double longitude) throws IOException, Exception {
775
776                String url = "/findNearestAddress?";
777
778                url = url + "&lat=" + latitude;
779                url = url + "&lng=" + longitude;
780                url = addUserName(url);
781
782                Element root = connectAndParse(url);
783                for (Object obj : root.getChildren("address")) {
784                        Element codeElement = (Element) obj;
785                        Address address = new Address();
786                        address.setStreet(codeElement.getChildText("street"));
787                        address.setStreetNumber(codeElement.getChildText("streetNumber"));
788                        address.setMtfcc(codeElement.getChildText("mtfcc"));
789
790                        address.setPostalCode(codeElement.getChildText("postalcode"));
791                        address.setPlaceName(codeElement.getChildText("placename"));
792                        address.setCountryCode(codeElement.getChildText("countryCode"));
793
794                        address.setLatitude(Double.parseDouble(codeElement.getChildText("lat")));
795                        address.setLongitude(Double.parseDouble(codeElement.getChildText("lng")));
796
797                        address.setAdminName1(codeElement.getChildText("adminName1"));
798                        address.setAdminCode1(codeElement.getChildText("adminCode1"));
799                        address.setAdminName2(codeElement.getChildText("adminName2"));
800                        address.setAdminCode2(codeElement.getChildText("adminCode2"));
801                        address.setAdminName3(codeElement.getChildText("adminName3"));
802                        address.setAdminCode3(codeElement.getChildText("adminCode3"));
803                        address.setAdminName4(codeElement.getChildText("adminName4"));
804                        address.setAdminCode4(codeElement.getChildText("adminCode4"));
805
806                        address.setDistance(Double.parseDouble(codeElement.getChildText("distance")));
807
808                        return address;
809                }
810
811                return null;
812        }
813
814        public static Intersection findNearestIntersection(double latitude, double longitude) throws Exception {
815                return findNearestIntersection(latitude, longitude, 0);
816        }
817
818        public static Intersection findNearestIntersection(double latitude, double longitude, double radius)
819                        throws Exception {
820
821                String url = "/findNearestIntersection?";
822
823                url = url + "&lat=" + latitude;
824                url = url + "&lng=" + longitude;
825                if (radius > 0) {
826                        url = url + "&radius=" + radius;
827                }
828                url = addUserName(url);
829
830                Element root = connectAndParse(url);
831                for (Object obj : root.getChildren("intersection")) {
832                        Element e = (Element) obj;
833                        Intersection intersection = new Intersection();
834                        intersection.setStreet1(e.getChildText("street1"));
835                        intersection.setStreet2(e.getChildText("street2"));
836                        intersection.setLatitude(Double.parseDouble(e.getChildText("lat")));
837                        intersection.setLongitude(Double.parseDouble(e.getChildText("lng")));
838                        intersection.setDistance(Double.parseDouble(e.getChildText("distance")));
839                        intersection.setPostalCode(e.getChildText("postalcode"));
840                        intersection.setPlaceName(e.getChildText("placename"));
841                        intersection.setCountryCode(e.getChildText("countryCode"));
842                        intersection.setAdminName2(e.getChildText("adminName2"));
843                        intersection.setAdminCode1(e.getChildText("adminCode1"));
844                        intersection.setAdminName1(e.getChildText("adminName1"));
845                        return intersection;
846                }
847                return null;
848        }
849
850        /**
851         * 
852         * @see <a * href=
853         *      "http://www.geonames.org/maps/reverse-geocoder.html#findNearbyStreets" >
854         *      web service documentation</a>
855         * 
856         * @param latitude
857         * @param longitude
858         * @param radius
859         * @return
860         * @throws Exception
861         */
862        public static List<StreetSegment> findNearbyStreets(double latitude, double longitude, double radius)
863                        throws Exception {
864
865                String url = "/findNearbyStreets?";
866
867                url = url + "&lat=" + latitude;
868                url = url + "&lng=" + longitude;
869                if (radius > 0) {
870                        url = url + "&radius=" + radius;
871                }
872                url = addUserName(url);
873
874                List<StreetSegment> segments = new ArrayList<StreetSegment>();
875
876                Element root = connectAndParse(url);
877                for (Object obj : root.getChildren("streetSegment")) {
878                        Element e = (Element) obj;
879                        StreetSegment streetSegment = new StreetSegment();
880                        String line = e.getChildText("line");
881                        String[] points = line.split(",");
882                        double[] latArray = new double[points.length];
883                        double[] lngArray = new double[points.length];
884                        for (int i = 0; i < points.length; i++) {
885                                String[] coords = points[i].split(" ");
886                                lngArray[i] = Double.parseDouble(coords[0]);
887                                latArray[i] = Double.parseDouble(coords[1]);
888                        }
889
890                        streetSegment.setCfcc(e.getChildText("cfcc"));
891                        streetSegment.setName(e.getChildText("name"));
892                        streetSegment.setFraddl(e.getChildText("fraddl"));
893                        streetSegment.setFraddr(e.getChildText("fraddr"));
894                        streetSegment.setToaddl(e.getChildText("toaddl"));
895                        streetSegment.setToaddr(e.getChildText("toaddr"));
896                        streetSegment.setPostalCode(e.getChildText("postalcode"));
897                        streetSegment.setPlaceName(e.getChildText("placename"));
898                        streetSegment.setCountryCode(e.getChildText("countryCode"));
899                        streetSegment.setAdminName2(e.getChildText("adminName2"));
900                        streetSegment.setAdminCode1(e.getChildText("adminCode1"));
901                        streetSegment.setAdminName1(e.getChildText("adminName1"));
902                        segments.add(streetSegment);
903                }
904                return segments;
905        }
906
907        public static List<StreetSegment> findNearbyStreetsOSM(double latitude, double longitude, double radius)
908                        throws Exception {
909
910                String url = "/findNearbyStreetsOSM?";
911
912                url = url + "&lat=" + latitude;
913                url = url + "&lng=" + longitude;
914                if (radius > 0) {
915                        url = url + "&radius=" + radius;
916                }
917                url = addUserName(url);
918
919                List<StreetSegment> segments = new ArrayList<StreetSegment>();
920
921                Element root = connectAndParse(url);
922                for (Object obj : root.getChildren("streetSegment")) {
923                        Element e = (Element) obj;
924                        StreetSegment streetSegment = new StreetSegment();
925                        String line = e.getChildText("line");
926                        String[] points = line.split(",");
927                        double[] latArray = new double[points.length];
928                        double[] lngArray = new double[points.length];
929                        for (int i = 0; i < points.length; i++) {
930                                String[] coords = points[i].split(" ");
931                                lngArray[i] = Double.parseDouble(coords[0]);
932                                latArray[i] = Double.parseDouble(coords[1]);
933                        }
934
935                        streetSegment.setName(e.getChildText("name"));
936                        segments.add(streetSegment);
937                }
938                return segments;
939        }
940
941        public static Address address(double latitude, double longitude) throws IOException, Exception {
942
943                String url = "/address?";
944
945                url = url + "&lat=" + latitude;
946                url = url + "&lng=" + longitude;
947                url = addUserName(url);
948
949                Element root = connectAndParse(url);
950                for (Object obj : root.getChildren("address")) {
951                        Element codeElement = (Element) obj;
952                        Address address = new Address();
953                        address.setStreet(codeElement.getChildText("street"));
954                        address.setStreetNumber(codeElement.getChildText("houseNumber"));
955
956                        address.setPostalCode(codeElement.getChildText("postalcode"));
957                        address.setPlaceName(codeElement.getChildText("locality"));
958                        address.setCountryCode(codeElement.getChildText("countryCode"));
959
960                        address.setLatitude(Double.parseDouble(codeElement.getChildText("lat")));
961                        address.setLongitude(Double.parseDouble(codeElement.getChildText("lng")));
962
963                        address.setAdminName1(codeElement.getChildText("adminName1"));
964                        address.setAdminCode1(codeElement.getChildText("adminCode1"));
965                        address.setAdminName2(codeElement.getChildText("adminName2"));
966                        address.setAdminCode2(codeElement.getChildText("adminCode2"));
967                        address.setAdminName3(codeElement.getChildText("adminName3"));
968                        address.setAdminCode3(codeElement.getChildText("adminCode3"));
969
970                        address.setSourceId(codeElement.getChildText("sourceId"));
971
972                        address.setDistance(Double.parseDouble(codeElement.getChildText("distance")));
973
974                        return address;
975                }
976
977                return null;
978        }
979
980        public static Address geoCodeAddress(String q, String country, String postalcode) throws IOException, Exception {
981
982                String url = "/geoCodeAddress?";
983
984                url = url + "&q=" + URLEncoder.encode(q, "UTF8");
985                if (country != null && !country.isEmpty()) {
986                        url = url + "&country=" + country;
987                }
988                if (postalcode != null && !postalcode.isEmpty()) {
989                        url = url + "&postalcode=" + URLEncoder.encode(postalcode, "UTF8");
990                }
991                url = addUserName(url);
992
993                Element root = connectAndParse(url);
994                for (Object obj : root.getChildren("address")) {
995                        Element codeElement = (Element) obj;
996                        Address address = new Address();
997                        address.setSourceId(codeElement.getChildText("sourceId"));
998                        address.setStreet(codeElement.getChildText("street"));
999                        address.setStreetNumber(codeElement.getChildText("houseNumber"));
1000
1001                        address.setPostalCode(codeElement.getChildText("postalcode"));
1002                        address.setPlaceName(codeElement.getChildText("locality"));
1003                        address.setCountryCode(codeElement.getChildText("countryCode"));
1004
1005                        address.setLatitude(Double.parseDouble(codeElement.getChildText("lat")));
1006                        address.setLongitude(Double.parseDouble(codeElement.getChildText("lng")));
1007
1008                        address.setAdminName1(codeElement.getChildText("adminName1"));
1009                        address.setAdminCode1(codeElement.getChildText("adminCode1"));
1010                        address.setAdminName2(codeElement.getChildText("adminName2"));
1011                        address.setAdminCode2(codeElement.getChildText("adminCode2"));
1012                        address.setAdminName3(codeElement.getChildText("adminName3"));
1013                        address.setAdminCode3(codeElement.getChildText("adminCode3"));
1014                        address.setAdminName4(codeElement.getChildText("adminName4"));
1015                        address.setAdminCode4(codeElement.getChildText("adminCode4"));
1016
1017                        return address;
1018                }
1019
1020                return null;
1021        }
1022
1023        /**
1024         * convenience method for {@link #search(ToponymSearchCriteria)}
1025         * 
1026         * @see <a href="http://www.geonames.org/export/geonames-search.html">search web
1027         *      service documentation</a>
1028         * 
1029         * @param q
1030         * @param countryCode
1031         * @param name
1032         * @param featureCodes
1033         * @param startRow
1034         * @return
1035         * @throws Exception
1036         */
1037        public static ToponymSearchResult search(String q, String countryCode, String name, String[] featureCodes,
1038                        int startRow) throws Exception {
1039                return search(q, countryCode, name, featureCodes, startRow, null, null, null);
1040        }
1041
1042        /**
1043         * convenience method for {@link #search(ToponymSearchCriteria)}
1044         * 
1045         * The string fields will be transparently utf8 encoded within the call.
1046         * 
1047         * @see <a href="http://www.geonames.org/export/geonames-search.html">search web
1048         *      service documentation</a>
1049         * 
1050         * @param q            search over all fields
1051         * @param countryCode
1052         * @param name         search over name only
1053         * @param featureCodes
1054         * @param startRow
1055         * @param language
1056         * @param style
1057         * @param exactName
1058         * @return
1059         * @throws Exception
1060         */
1061        public static ToponymSearchResult search(String q, String countryCode, String name, String[] featureCodes,
1062                        int startRow, String language, Style style, String exactName) throws Exception {
1063                ToponymSearchCriteria searchCriteria = new ToponymSearchCriteria();
1064                searchCriteria.setQ(q);
1065                searchCriteria.setCountryCode(countryCode);
1066                searchCriteria.setName(name);
1067                searchCriteria.setFeatureCodes(featureCodes);
1068                searchCriteria.setStartRow(startRow);
1069                searchCriteria.setLanguage(language);
1070                searchCriteria.setStyle(style);
1071                searchCriteria.setNameEquals(exactName);
1072                return search(searchCriteria);
1073        }
1074
1075        /**
1076         * full text search on the GeoNames database.
1077         * 
1078         * This service gets the number of toponyms defined by the 'maxRows' parameter.
1079         * The parameter 'style' determines which fields are returned by the service.
1080         * 
1081         * @see <a href="http://www.geonames.org/export/geonames-search.html">search web
1082         *      service documentation</a>
1083         * 
1084         *      <br>
1085         * 
1086         *      <pre>
1087         *      ToponymSearchCriteria searchCriteria = new ToponymSearchCriteria();
1088         *      searchCriteria.setQ(&quot;z&amp;uumlrich&quot;);
1089         *      ToponymSearchResult searchResult = WebService.search(searchCriteria);
1090         *      for (Toponym toponym : searchResult.toponyms) {
1091         *              System.out.println(toponym.getName() + &quot; &quot; + toponym.getCountryName());
1092         *      }
1093         *      </pre>
1094         * 
1095         * 
1096         * @param searchCriteria
1097         * @return
1098         * @throws Exception
1099         */
1100        public static ToponymSearchResult search(ToponymSearchCriteria searchCriteria) throws Exception {
1101                ToponymSearchResult searchResult = new ToponymSearchResult();
1102
1103                String url = "/search?";
1104
1105                if (searchCriteria.getQ() != null) {
1106                        url = url + "q=" + URLEncoder.encode(searchCriteria.getQ(), "UTF8");
1107                }
1108                if (searchCriteria.getNameEquals() != null) {
1109                        url = url + "&name_equals=" + URLEncoder.encode(searchCriteria.getNameEquals(), "UTF8");
1110                }
1111                if (searchCriteria.getNameStartsWith() != null) {
1112                        url = url + "&name_startsWith=" + URLEncoder.encode(searchCriteria.getNameStartsWith(), "UTF8");
1113                }
1114
1115                if (searchCriteria.getName() != null) {
1116                        url = url + "&name=" + URLEncoder.encode(searchCriteria.getName(), "UTF8");
1117                }
1118
1119                if (searchCriteria.getTag() != null) {
1120                        url = url + "&tag=" + URLEncoder.encode(searchCriteria.getTag(), "UTF8");
1121                }
1122
1123                if (searchCriteria.getCountryCode() != null) {
1124                        url = url + "&country=" + searchCriteria.getCountryCode();
1125                }
1126                if (searchCriteria.getCountryCodes() != null) {
1127                        for (String countryCode : searchCriteria.getCountryCodes()) {
1128                                url = url + "&country=" + countryCode;
1129                        }
1130                }
1131                if (searchCriteria.getCountryBias() != null) {
1132                        if (!url.endsWith("&")) {
1133                                url = url + "&";
1134                        }
1135                        url = url + "countryBias=" + searchCriteria.getCountryBias();
1136                }
1137                if (searchCriteria.getContinentCode() != null) {
1138                        url = url + "&continentCode=" + searchCriteria.getContinentCode();
1139                }
1140
1141                if (searchCriteria.getAdminCode1() != null) {
1142                        url = url + "&adminCode1=" + URLEncoder.encode(searchCriteria.getAdminCode1(), "UTF8");
1143                }
1144                if (searchCriteria.getAdminCode2() != null) {
1145                        url = url + "&adminCode2=" + URLEncoder.encode(searchCriteria.getAdminCode2(), "UTF8");
1146                }
1147                if (searchCriteria.getAdminCode3() != null) {
1148                        url = url + "&adminCode3=" + URLEncoder.encode(searchCriteria.getAdminCode3(), "UTF8");
1149                }
1150                if (searchCriteria.getAdminCode4() != null) {
1151                        url = url + "&adminCode4=" + URLEncoder.encode(searchCriteria.getAdminCode4(), "UTF8");
1152                }
1153
1154                if (searchCriteria.getLanguage() != null) {
1155                        url = url + "&lang=" + searchCriteria.getLanguage();
1156                }
1157
1158                if (searchCriteria.getFeatureClass() != null) {
1159                        url = url + "&featureClass=" + searchCriteria.getFeatureClass();
1160                }
1161
1162                if (searchCriteria.getFeatureCodes() != null) {
1163                        for (String featureCode : searchCriteria.getFeatureCodes()) {
1164                                url = url + "&fcode=" + featureCode;
1165                        }
1166                }
1167                if (searchCriteria.getMaxRows() > 0) {
1168                        url = url + "&maxRows=" + searchCriteria.getMaxRows();
1169                }
1170                if (searchCriteria.getStartRow() > 0) {
1171                        url = url + "&startRow=" + searchCriteria.getStartRow();
1172                }
1173                if (searchCriteria.getFuzzy() != 1.0) {
1174                        url = url + "&fuzzy=" + searchCriteria.getFuzzy();
1175                }
1176
1177                if (searchCriteria.getBoundingBox() != null) {
1178                        url = url + "&east=" + searchCriteria.getBoundingBox().getEast();
1179                        url = url + "&west=" + searchCriteria.getBoundingBox().getWest();
1180                        url = url + "&north=" + searchCriteria.getBoundingBox().getNorth();
1181                        url = url + "&south=" + searchCriteria.getBoundingBox().getSouth();
1182                }
1183
1184                if (searchCriteria.getStyle() != null) {
1185                        url = url + "&style=" + searchCriteria.getStyle();
1186                } else {
1187                        url = addDefaultStyle(url);
1188                }
1189                url = addUserName(url);
1190
1191                Element root = connectAndParse(url);
1192                searchResult.totalResultsCount = Integer.parseInt(root.getChildText("totalResultsCount"));
1193                searchResult.setStyle(Style.valueOf(root.getAttributeValue("style")));
1194
1195                for (Object obj : root.getChildren("geoname")) {
1196                        Element toponymElement = (Element) obj;
1197                        Toponym toponym = getToponymFromElement(toponymElement);
1198                        toponym.setStyle(searchResult.getStyle());
1199                        searchResult.toponyms.add(toponym);
1200                }
1201
1202                return searchResult;
1203        }
1204
1205        /**
1206         * returns the children in the administrative hierarchy of a toponym. With
1207         * default maxRows.
1208         * 
1209         * @param geonameId
1210         * @param language
1211         * @param style
1212         * @return
1213         * @throws Exception
1214         */
1215        public static ToponymSearchResult children(int geonameId, String language, Style style) throws Exception {
1216                return children(geonameId, language, style, 0);
1217        }
1218
1219        /**
1220         * 
1221         * @param geonameId
1222         * @param language
1223         * @param style
1224         * @param maxRows
1225         * @return
1226         * @throws Exception
1227         */
1228        public static ToponymSearchResult children(int geonameId, String language, Style style, int maxRows)
1229                        throws Exception {
1230
1231                ToponymSearchResult searchResult = new ToponymSearchResult();
1232
1233                String url = "/children?";
1234
1235                url = url + "geonameId=" + geonameId;
1236
1237                if (language != null) {
1238                        url = url + "&lang=" + language;
1239                }
1240                if (maxRows != 0) {
1241                        url += "&maxRows=" + maxRows;
1242                }
1243
1244                if (style != null) {
1245                        url = url + "&style=" + style;
1246                } else {
1247                        url = addDefaultStyle(url);
1248                }
1249                url = addUserName(url);
1250
1251                Element root = connectAndParse(url);
1252                searchResult.totalResultsCount = Integer.parseInt(root.getChildText("totalResultsCount"));
1253                searchResult.setStyle(Style.valueOf(root.getAttributeValue("style")));
1254
1255                for (Object obj : root.getChildren("geoname")) {
1256                        Element toponymElement = (Element) obj;
1257                        Toponym toponym = getToponymFromElement(toponymElement);
1258                        searchResult.toponyms.add(toponym);
1259                }
1260
1261                return searchResult;
1262        }
1263
1264        /**
1265         * returns the neighbours of a toponym.
1266         * 
1267         * @param geonameId
1268         * @param language
1269         * @param style
1270         * @return
1271         * @throws Exception
1272         */
1273        public static ToponymSearchResult neighbours(int geonameId, String language, Style style) throws Exception {
1274                ToponymSearchResult searchResult = new ToponymSearchResult();
1275
1276                String url = "/neighbours?";
1277
1278                url = url + "geonameId=" + geonameId;
1279
1280                if (language != null) {
1281                        url = url + "&lang=" + language;
1282                }
1283
1284                if (style != null) {
1285                        url = url + "&style=" + style;
1286                } else {
1287                        url = addDefaultStyle(url);
1288                }
1289                url = addUserName(url);
1290
1291                Element root = connectAndParse(url);
1292                searchResult.totalResultsCount = Integer.parseInt(root.getChildText("totalResultsCount"));
1293                searchResult.setStyle(Style.valueOf(root.getAttributeValue("style")));
1294
1295                for (Object obj : root.getChildren("geoname")) {
1296                        Element toponymElement = (Element) obj;
1297                        Toponym toponym = getToponymFromElement(toponymElement);
1298                        searchResult.toponyms.add(toponym);
1299                }
1300
1301                return searchResult;
1302        }
1303
1304        /**
1305         * returns the hierarchy for a geonameId
1306         * 
1307         * @see <a href=
1308         *      "http://www.geonames.org/export/place-hierarchy.html#hierarchy">Hierarchy
1309         *      service description</a>
1310         * 
1311         * @param geonameId
1312         * @param language
1313         * @param style
1314         * @return
1315         * @throws Exception
1316         */
1317        public static List<Toponym> hierarchy(int geonameId, String language, Style style) throws Exception {
1318
1319                String url = "/hierarchy?";
1320
1321                url = url + "geonameId=" + geonameId;
1322
1323                if (language != null) {
1324                        url = url + "&lang=" + language;
1325                }
1326
1327                if (style != null) {
1328                        url = url + "&style=" + style;
1329                } else {
1330                        url = addDefaultStyle(url);
1331                }
1332                url = addUserName(url);
1333
1334                Element root = connectAndParse(url);
1335                List<Toponym> toponyms = new ArrayList<Toponym>();
1336                for (Object obj : root.getChildren("geoname")) {
1337                        Element toponymElement = (Element) obj;
1338                        Toponym toponym = getToponymFromElement(toponymElement);
1339                        toponyms.add(toponym);
1340                }
1341
1342                return toponyms;
1343        }
1344
1345        public static void saveTags(String[] tags, Toponym toponym, String username, String password) throws Exception {
1346                if (toponym.getGeoNameId() == 0) {
1347                        throw new Error("no geonameid specified");
1348                }
1349
1350                // FIXME proper url
1351                String url = "/servlet/geonames?srv=61";
1352
1353                url = url + "&geonameId=" + toponym.getGeoNameId();
1354                url = addUserName(url);
1355
1356                StringBuilder tagsCommaseparated = new StringBuilder();
1357                for (String tag : tags) {
1358                        tagsCommaseparated.append(tag + ",");
1359                }
1360                url = url + "&tag=" + tagsCommaseparated;
1361
1362                Element root = connectAndParse(url);
1363        }
1364
1365        /**
1366         * full text search on geolocated wikipedia articles.
1367         * 
1368         * @param q
1369         * @param language
1370         * @return
1371         * @throws Exception
1372         */
1373        public static List<WikipediaArticle> wikipediaSearch(String q, String language) throws Exception {
1374                List<WikipediaArticle> articles = new ArrayList<WikipediaArticle>();
1375
1376                String url = "/wikipediaSearch?";
1377
1378                url = url + "q=" + URLEncoder.encode(q, "UTF8");
1379
1380                if (language != null) {
1381                        url = url + "&lang=" + language;
1382                }
1383                url = addUserName(url);
1384
1385                Element root = connectAndParse(url);
1386                for (Object obj : root.getChildren("entry")) {
1387                        Element wikipediaArticleElement = (Element) obj;
1388                        WikipediaArticle wikipediaArticle = getWikipediaArticleFromElement(wikipediaArticleElement);
1389                        articles.add(wikipediaArticle);
1390                }
1391
1392                return articles;
1393        }
1394
1395        /**
1396         * full text search on geolocated wikipedia articles.
1397         * 
1398         * @param title
1399         * @param language
1400         * @return
1401         * @throws Exception
1402         */
1403        public static List<WikipediaArticle> wikipediaSearchForTitle(String title, String language) throws Exception {
1404                List<WikipediaArticle> articles = new ArrayList<WikipediaArticle>();
1405
1406                String url = "/wikipediaSearch?";
1407
1408                url = url + "title=" + URLEncoder.encode(title, "UTF8");
1409
1410                if (language != null) {
1411                        url = url + "&lang=" + language;
1412                }
1413                url = addUserName(url);
1414
1415                Element root = connectAndParse(url);
1416                for (Object obj : root.getChildren("entry")) {
1417                        Element wikipediaArticleElement = (Element) obj;
1418                        WikipediaArticle wikipediaArticle = getWikipediaArticleFromElement(wikipediaArticleElement);
1419                        articles.add(wikipediaArticle);
1420                }
1421
1422                return articles;
1423        }
1424
1425        public static List<WikipediaArticle> findNearbyWikipedia(double latitude, double longitude, String language)
1426                        throws Exception {
1427                return findNearbyWikipedia(latitude, longitude, 0, language, 0);
1428        }
1429
1430        /* Overload function to allow backward compatibility */
1431        /**
1432         * Based on the following inform: Webservice Type : REST
1433         * api.geonames.org/findNearbyWikipedia? Parameters : lang : language code
1434         * (around 240 languages) (default = en) lat,lng, radius (in km), maxRows
1435         * (default = 5) Example:
1436         * http://api.geonames.org/findNearbyWikipedia?lat=47&lng=9
1437         * 
1438         * @param: latitude
1439         * @param: longitude
1440         * @param: radius
1441         * @param: language
1442         * @param: maxRows
1443         * @return: list of wikipedia articles
1444         * @throws: Exception
1445         */
1446        public static List<WikipediaArticle> findNearbyWikipedia(double latitude, double longitude, double radius,
1447                        String language, int maxRows) throws Exception {
1448
1449                List<WikipediaArticle> articles = new ArrayList<WikipediaArticle>();
1450
1451                String url = "/findNearbyWikipedia?";
1452
1453                url = url + "lat=" + latitude;
1454                url = url + "&lng=" + longitude;
1455                if (radius > 0) {
1456                        url = url + "&radius=" + radius;
1457                }
1458                if (maxRows > 0) {
1459                        url = url + "&maxRows=" + maxRows;
1460                }
1461
1462                if (language != null) {
1463                        url = url + "&lang=" + language;
1464                }
1465                url = addUserName(url);
1466
1467                Element root = connectAndParse(url);
1468                for (Object obj : root.getChildren("entry")) {
1469                        Element wikipediaArticleElement = (Element) obj;
1470                        WikipediaArticle wikipediaArticle = getWikipediaArticleFromElement(wikipediaArticleElement);
1471                        articles.add(wikipediaArticle);
1472                }
1473
1474                return articles;
1475        }
1476
1477        /**
1478         * GTOPO30 is a global digital elevation model (DEM) with a horizontal grid
1479         * spacing of 30 arc seconds (approximately 1 kilometer). GTOPO30 was derived
1480         * from several raster and vector sources of topographic information.
1481         * 
1482         * @param latitude
1483         * @param longitude
1484         * @return a single number giving the elevation in meters according to gtopo30,
1485         *         ocean areas have been masked as "no data" and have been assigned a
1486         *         value of -9999
1487         * @throws IOException
1488         * @throws GeoNamesException
1489         */
1490        public static int gtopo30(double latitude, double longitude) throws IOException, GeoNamesException {
1491                String url = "/gtopo30?lat=" + latitude + "&lng=" + longitude;
1492                url = addUserName(url);
1493                BufferedReader in = new BufferedReader(new InputStreamReader(connect(url)));
1494                String gtopo30 = in.readLine();
1495                in.close();
1496                checkException(gtopo30);
1497                return Integer.parseInt(gtopo30);
1498        }
1499
1500        /**
1501         * Shuttle Radar Topography Mission (SRTM) elevation data. SRTM consisted of a
1502         * specially modified radar system that flew onboard the Space Shuttle Endeavour
1503         * during an 11-day mission in February of 2000. The dataset covers land areas
1504         * between 60 degrees north and 56 degrees south. This web service is using
1505         * SRTM3 data with data points located every 3-arc-second (approximately 90
1506         * meters) on a latitude/longitude grid.
1507         * 
1508         * @param latitude
1509         * @param longitude
1510         * @return elevation or -32768 if unknown
1511         * @throws IOException
1512         * @throws GeoNamesException
1513         */
1514        public static int srtm3(double latitude, double longitude) throws IOException, GeoNamesException {
1515                String url = "/srtm3?lat=" + latitude + "&lng=" + longitude;
1516                url = addUserName(url);
1517                BufferedReader in = new BufferedReader(new InputStreamReader(connect(url)));
1518                String srtm3 = in.readLine();
1519                in.close();
1520                checkException(srtm3);
1521                return Integer.parseInt(srtm3);
1522        }
1523
1524        public static int[] srtm3(double[] latitude, double[] longitude) throws IOException {
1525                if (latitude.length != longitude.length) {
1526                        throw new Error("number of lats and longs must be equal");
1527                }
1528                int[] elevation = new int[latitude.length];
1529                String lats = "";
1530                String lngs = "";
1531                for (int i = 0; i < elevation.length; i++) {
1532                        lats += latitude[i] + ",";
1533                        lngs += longitude[i] + ",";
1534                }
1535                String url = "/srtm3?lats=" + lats + "&lngs=" + lngs;
1536                url = addUserName(url);
1537                BufferedReader in = new BufferedReader(new InputStreamReader(connect(url)));
1538                for (int i = 0; i < elevation.length; i++) {
1539                        String srtm3 = in.readLine();
1540                        elevation[i] = Integer.parseInt(srtm3);
1541                }
1542                in.close();
1543                return elevation;
1544        }
1545
1546        public static int srtm1(double latitude, double longitude) throws IOException, GeoNamesException {
1547                String url = "/srtm1?lat=" + latitude + "&lng=" + longitude;
1548                url = addUserName(url);
1549                BufferedReader in = new BufferedReader(new InputStreamReader(connect(url)));
1550                String srtm1 = in.readLine();
1551                in.close();
1552                checkException(srtm1);
1553                return Integer.parseInt(srtm1);
1554        }
1555
1556        public static int[] srtm1(double[] latitude, double[] longitude) throws IOException {
1557                if (latitude.length != longitude.length) {
1558                        throw new Error("number of lats and longs must be equal");
1559                }
1560                int[] elevation = new int[latitude.length];
1561                String lats = "";
1562                String lngs = "";
1563                for (int i = 0; i < elevation.length; i++) {
1564                        lats += latitude[i] + ",";
1565                        lngs += longitude[i] + ",";
1566                }
1567                String url = "/srtm1?lats=" + lats + "&lngs=" + lngs;
1568                url = addUserName(url);
1569                BufferedReader in = new BufferedReader(new InputStreamReader(connect(url)));
1570                for (int i = 0; i < elevation.length; i++) {
1571                        String srtm1 = in.readLine();
1572                        elevation[i] = Integer.parseInt(srtm1);
1573                }
1574                in.close();
1575                return elevation;
1576        }
1577
1578        public static int astergdem(double latitude, double longitude) throws IOException, GeoNamesException {
1579                String url = "/astergdem?lat=" + latitude + "&lng=" + longitude;
1580                url = addUserName(url);
1581                BufferedReader in = new BufferedReader(new InputStreamReader(connect(url)));
1582                String astergdem = in.readLine();
1583                in.close();
1584                checkException(astergdem);
1585                return Integer.parseInt(astergdem);
1586        }
1587
1588        public static int[] astergdem(double[] latitude, double[] longitude) throws IOException {
1589                if (latitude.length != longitude.length) {
1590                        throw new Error("number of lats and longs must be equal");
1591                }
1592                int[] elevation = new int[latitude.length];
1593                String lats = "";
1594                String lngs = "";
1595                for (int i = 0; i < elevation.length; i++) {
1596                        lats += latitude[i] + ",";
1597                        lngs += longitude[i] + ",";
1598                }
1599                String url = "/astergdem?lats=" + lats + "&lngs=" + lngs;
1600                url = addUserName(url);
1601                BufferedReader in = new BufferedReader(new InputStreamReader(connect(url)));
1602                for (int i = 0; i < elevation.length; i++) {
1603                        String astergdem = in.readLine();
1604                        elevation[i] = Integer.parseInt(astergdem);
1605                }
1606                in.close();
1607                return elevation;
1608        }
1609
1610        /**
1611         * The iso country code of any given point. It is calling
1612         * {@link #countryCode(double, double, double)} with radius=0.0
1613         * 
1614         * @param latitude
1615         * @param longitude
1616         * @return
1617         * @throws IOException
1618         * @throws GeoNamesException
1619         */
1620        public static String countryCode(double latitude, double longitude) throws IOException, GeoNamesException {
1621                return countryCode(latitude, longitude, 0);
1622        }
1623
1624        /**
1625         * The iso country code of any given point with radius for coastal areas.
1626         * 
1627         * @param latitude
1628         * @param longitude
1629         * @param radius (optional)
1630         * 
1631         * @return iso country code for the given latitude/longitude
1632         * @throws IOException
1633         * @throws GeoNamesException
1634         */
1635        public static String countryCode(double latitude, double longitude, double radius)
1636                        throws IOException, GeoNamesException {
1637                String url = "/countryCode?lat=" + latitude + "&lng=" + longitude;
1638                if (radius != 0) {
1639                        url += "&radius=" + radius;
1640                }
1641                url = addUserName(url);
1642                BufferedReader in = new BufferedReader(new InputStreamReader(connect(url)));
1643                String cc = in.readLine();
1644                in.close();
1645                if (cc != null && cc.length() == 2) {
1646                        return cc;
1647                }
1648                if (cc == null || cc.length() == 0) {
1649                        // nothing found return null
1650                        return null;
1651                }
1652                // check whether we can parse an exception and throw it if we can
1653                checkException(cc);
1654                // something else was wrong, through generic exception
1655                throw new GeoNamesException("unhandled exception");
1656        }
1657        
1658        /**
1659         * 
1660         * @param latitude
1661         * @param longitude
1662         * @param radius (optional)
1663         * @param level (optional)
1664         * @param lang (optional)
1665         * @return
1666         * @throws IOException
1667         * @throws Exception
1668         */
1669        public static CountrySubdivision countrySubdivision(double latitude, double longitude, double radius, int level, String lang)
1670                        throws IOException, Exception {
1671                String url = "/countrySubdivision?lat=" + latitude + "&lng=" + longitude;
1672                if (radius != 0) {
1673                        url += "&radius=" + radius;
1674                }
1675                if (level != 0) {
1676                        url += "&level=" + level;
1677                }
1678                if (lang!=null) {
1679                        url += "&lang=" + level;
1680                }
1681                url = addUserName(url);
1682                Element root = connectAndParse(url);
1683                for (Object obj : root.getChildren("countrySubdivision")) {
1684                        Element codeElement = (Element) obj;
1685                        CountrySubdivision countrySubdivision = new CountrySubdivision();
1686                        
1687                        countrySubdivision.setCountryCode(codeElement.getChildText("countryCode"));
1688                        countrySubdivision.setCountryName(codeElement.getChildText("countryName"));
1689
1690                        countrySubdivision.setAdminName1(codeElement.getChildText("adminName1"));
1691                        countrySubdivision.setAdminCode1(codeElement.getChildText("adminCode1"));
1692                        countrySubdivision.setAdminName2(codeElement.getChildText("adminName2"));
1693                        countrySubdivision.setAdminCode2(codeElement.getChildText("adminCode2"));
1694                        countrySubdivision.setAdminName3(codeElement.getChildText("adminName3"));
1695                        countrySubdivision.setAdminCode3(codeElement.getChildText("adminCode3"));
1696                        countrySubdivision.setAdminName4(codeElement.getChildText("adminName4"));
1697                        countrySubdivision.setAdminCode4(codeElement.getChildText("adminCode4"));
1698                        countrySubdivision.setAdminName5(codeElement.getChildText("adminName5"));
1699                        countrySubdivision.setAdminCode5(codeElement.getChildText("adminCode5"));
1700
1701                        countrySubdivision.setDistance(Double.parseDouble(codeElement.getChildText("distance")));
1702
1703                        return countrySubdivision;
1704                }
1705
1706                return null;
1707        }
1708
1709        /**
1710         * get the timezone for a given location
1711         * 
1712         * @param latitude
1713         * @param longitude
1714         * @return timezone at the given location
1715         * @throws IOException
1716         * @throws Exception
1717         */
1718        public static Timezone timezone(double latitude, double longitude) throws IOException, Exception {
1719
1720                String url = "/timezone?";
1721                double radius = 0;
1722
1723                url = url + "&lat=" + latitude;
1724                url = url + "&lng=" + longitude;
1725                if (radius > 0) {
1726                        url = url + "&radius=" + radius;
1727                }
1728                url = addUserName(url);
1729
1730                Element root = connectAndParse(url);
1731                for (Object obj : root.getChildren("timezone")) {
1732                        Element codeElement = (Element) obj;
1733                        Timezone timezone = new Timezone();
1734                        timezone.setTimezoneId(codeElement.getChildText("timezoneId"));
1735                        timezone.setCountryCode(codeElement.getChildText("countryCode"));
1736
1737                        if (codeElement.getChildText("time") != null) {
1738                                String minuteDateFmt = "yyyy-MM-dd HH:mm";
1739                                SimpleDateFormat df = null;
1740                                if (codeElement.getChildText("time").length() == minuteDateFmt.length()) {
1741                                        df = new SimpleDateFormat(minuteDateFmt);
1742                                } else {
1743                                        df = new SimpleDateFormat(DATEFMT);
1744                                }
1745                                timezone.setTime(df.parse(codeElement.getChildText("time")));
1746                                if (codeElement.getChildText("sunrise") != null) {
1747                                        timezone.setSunrise(df.parse(codeElement.getChildText("sunrise")));
1748                                }
1749                                if (codeElement.getChildText("sunset") != null) {
1750                                        timezone.setSunset(df.parse(codeElement.getChildText("sunset")));
1751                                }
1752                                timezone.setGmtOffset(Double.parseDouble(codeElement.getChildText("gmtOffset")));
1753                                timezone.setDstOffset(Double.parseDouble(codeElement.getChildText("dstOffset")));
1754                        }
1755                        return timezone;
1756                }
1757
1758                return null;
1759        }
1760
1761        // FIXME implement and test
1762        public static String ocean(double latitude, double longitude) throws IOException, Exception {
1763
1764                String url = "/ocean?";
1765                double radius = 0;
1766
1767                url = url + "&lat=" + latitude;
1768                url = url + "&lng=" + longitude;
1769                if (radius > 0) {
1770                        url = url + "&radius=" + radius;
1771                }
1772                url = addUserName(url);
1773
1774                Element root = connectAndParse(url);
1775                for (Object obj : root.getChildren("ocean")) {
1776                        Element oceanElement = (Element) obj;
1777                        if (oceanElement != null) {
1778                                return oceanElement.getChildText("name");
1779                        }
1780                }
1781
1782                return null;
1783        }
1784
1785        /**
1786         * 
1787         * @param latitude
1788         * @param longitude
1789         * @return
1790         * @throws IOException
1791         * @throws Exception
1792         */
1793        public static WeatherObservation findNearByWeather(double latitude, double longitude)
1794                        throws IOException, Exception {
1795
1796                String url = "/findNearByWeatherXML?";
1797
1798                url = url + "&lat=" + latitude;
1799                url = url + "&lng=" + longitude;
1800                url = addUserName(url);
1801
1802                Element root = connectAndParse(url);
1803                for (Object obj : root.getChildren("observation")) {
1804                        Element weatherObservationElement = (Element) obj;
1805                        WeatherObservation weatherObservation = getWeatherObservationFromElement(weatherObservationElement);
1806                        return weatherObservation;
1807                }
1808
1809                return null;
1810        }
1811
1812        public static WeatherObservation weatherIcao(String icaoCode) throws IOException, Exception {
1813
1814                String url = "/weatherIcaoXML?";
1815
1816                url = url + "&ICAO=" + icaoCode;
1817                url = addUserName(url);
1818
1819                Element root = connectAndParse(url);
1820                for (Object obj : root.getChildren("observation")) {
1821                        Element weatherObservationElement = (Element) obj;
1822                        WeatherObservation weatherObservation = getWeatherObservationFromElement(weatherObservationElement);
1823                        return weatherObservation;
1824                }
1825
1826                return null;
1827        }
1828
1829        /**
1830         * @return the geoNamesServer, default is http://api.geonames.org
1831         */
1832        public static String getGeoNamesServer() {
1833                return geoNamesServer;
1834        }
1835
1836        /**
1837         * @return the geoNamesServerFailover
1838         */
1839        public static String getGeoNamesServerFailover() {
1840                return geoNamesServerFailover;
1841        }
1842
1843        /**
1844         * sets the server name for the GeoNames server to be used for the requests.
1845         * Default is api.geonames.org
1846         * 
1847         * @param geoNamesServer the geonamesServer to set
1848         */
1849        public static void setGeoNamesServer(String pGeoNamesServer) {
1850                if (pGeoNamesServer == null) {
1851                        throw new Error();
1852                }
1853                pGeoNamesServer = pGeoNamesServer.trim().toLowerCase();
1854                // add default http protocol if it is missing
1855                if (!pGeoNamesServer.startsWith("http://") && !pGeoNamesServer.startsWith("https://")) {
1856                        pGeoNamesServer = "http://" + pGeoNamesServer;
1857                }
1858                WebService.geoNamesServer = pGeoNamesServer;
1859        }
1860
1861        /**
1862         * sets the default failover server for requests in case the main server is not
1863         * accessible. Default is api.geonames.org<br>
1864         * The failover server is only called if it is different from the main
1865         * server.<br>
1866         * The failover server is used for commercial GeoNames web service users.
1867         * 
1868         * @param geoNamesServerFailover the geoNamesServerFailover to set
1869         */
1870        public static void setGeoNamesServerFailover(String geoNamesServerFailover) {
1871                if (geoNamesServerFailover != null) {
1872                        geoNamesServerFailover = geoNamesServerFailover.trim().toLowerCase();
1873                        if (!geoNamesServerFailover.startsWith("http://") && !geoNamesServerFailover.startsWith("https://")) {
1874                                geoNamesServerFailover = "http://" + geoNamesServerFailover;
1875                        }
1876                }
1877                WebService.geoNamesServerFailover = geoNamesServerFailover;
1878        }
1879
1880        /**
1881         * @return the proxy
1882         */
1883        public static Proxy getProxy() {
1884                return proxy;
1885        }
1886
1887        /**
1888         * @param proxy the proxy to set
1889         * 
1890         *              If you are behind a proxy and cannot change the java system
1891         *              properties, you can use this method to set a proxy. You define
1892         *              it like this:
1893         * 
1894         *              <pre>
1895         * <code>
1896         *            java.net.SocketAddress sa = new java.net.InetSocketAddress("myproxyserver", 8080);
1897         *            java.net.Proxy proxy = new java.net.Proxy(java.net.Proxy.Type.HTTP, sa);
1898         *            </code>
1899         *              </pre>
1900         */
1901        public static void setProxy(Proxy proxy) {
1902                WebService.proxy = proxy;
1903        }
1904
1905        /**
1906         * @return the userName
1907         */
1908        public static String getUserName() {
1909                return userName;
1910        }
1911
1912        /**
1913         * Sets the user name to be used for the requests. Needed to access the
1914         * commercial GeoNames web services.
1915         * 
1916         * @param userName the userName to set
1917         */
1918        public static void setUserName(String userName) {
1919                WebService.userName = userName;
1920        }
1921
1922        /**
1923         * @return the token
1924         */
1925        public static String getToken() {
1926                return token;
1927        }
1928
1929        /**
1930         * sets the token to be used to authenticate the requests. This is an optional
1931         * parameter for the commercial version of the GeoNames web services.
1932         * 
1933         * @param token the token to set
1934         */
1935        public static void setToken(String token) {
1936                WebService.token = token;
1937        }
1938
1939        /**
1940         * @return the defaultStyle
1941         */
1942        public static Style getDefaultStyle() {
1943                return defaultStyle;
1944        }
1945
1946        /**
1947         * @param defaultStyle the defaultStyle to set
1948         */
1949        public static void setDefaultStyle(Style defaultStyle) {
1950                WebService.defaultStyle = defaultStyle;
1951        }
1952
1953        /**
1954         * @return the readTimeOut
1955         */
1956        public static int getReadTimeOut() {
1957                return readTimeOut;
1958        }
1959
1960        /**
1961         * @param readTimeOut the readTimeOut to set
1962         */
1963        public static void setReadTimeOut(int readTimeOut) {
1964                WebService.readTimeOut = readTimeOut;
1965        }
1966
1967        /**
1968         * @return the connectTimeOut
1969         */
1970        public static int getConnectTimeOut() {
1971                return connectTimeOut;
1972        }
1973
1974        /**
1975         * @param connectTimeOut the connectTimeOut to set
1976         */
1977        public static void setConnectTimeOut(int connectTimeOut) {
1978                WebService.connectTimeOut = connectTimeOut;
1979        }
1980
1981}