Add address line 2 support to generator
[zxing.git] / zxing.appspot.com / generator / src / com / google / zxing / web / generator / client / GeoLocationGenerator.java
1 /*
2  * Copyright (C) 2008 ZXing authors
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16
17 package com.google.zxing.web.generator.client;
18
19 import com.google.gwt.http.client.URL;
20 import com.google.gwt.maps.client.MapWidget;
21 import com.google.gwt.maps.client.control.SmallMapControl;
22 import com.google.gwt.maps.client.event.MapClickHandler;
23 import com.google.gwt.maps.client.event.MarkerDragEndHandler;
24 import com.google.gwt.maps.client.event.MapClickHandler.MapClickEvent;
25 import com.google.gwt.maps.client.geom.LatLng;
26 import com.google.gwt.maps.client.overlay.Marker;
27 import com.google.gwt.maps.client.overlay.MarkerOptions;
28 import com.google.gwt.user.client.ui.Button;
29 import com.google.gwt.user.client.ui.ChangeListener;
30 import com.google.gwt.user.client.ui.ClickListener;
31 import com.google.gwt.user.client.ui.Grid;
32 import com.google.gwt.user.client.ui.SimplePanel;
33 import com.google.gwt.user.client.ui.TextBox;
34 import com.google.gwt.user.client.ui.Widget;
35
36 /**
37  * A generator for geo location. It also accepts a google maps links and
38  * extracts the coordinates and query from the URL.
39  * 
40  * @author Yohann Coppel
41  */
42 public class GeoLocationGenerator implements GeneratorSource, ChangeListener {
43   private static final String LON_REGEXP = "[+-]?[0-9]+(.[0-9]+)?";
44   private static final String LAT_REGEXP = "[+-]?[0-9]+(.[0-9]+)?";
45   
46   Grid table = null;
47   TextBox latitude = new TextBox();
48   TextBox longitude = new TextBox();
49   TextBox query = new TextBox();
50   TextBox mapsLink = new TextBox();
51   MapWidget map = new MapWidget();
52   Marker mapMarker = null;
53   private ChangeListener changeListener;
54   
55   public GeoLocationGenerator(ChangeListener listener) {
56     this.changeListener = listener;
57     latitude.addStyleName(StylesDefs.INPUT_FIELD_REQUIRED);
58     longitude.addStyleName(StylesDefs.INPUT_FIELD_REQUIRED);
59     latitude.addChangeListener(listener);
60     latitude.addChangeListener(this);
61     longitude.addChangeListener(listener);
62     longitude.addChangeListener(this);
63     query.addChangeListener(listener);
64   }
65   
66   public String getName() {
67     return "Geo location";
68   }
69
70   public String getText() throws GeneratorException {
71     String que = getQueryField();
72     if (null != que && que.length() > 0) {
73       if (null == getLatitudeField()) {
74         latitude.setText("0");
75       }
76       if (null == getLongitudeField()) {
77         longitude.setText("0");
78       }
79     }
80     String lat = getLatitudeField();
81     String lon = getLongitudeField();
82     
83     if (null != que && que.length() > 0) {
84       return "geo:"+lat+ ',' +lon+"?q="+que;
85     }
86
87     return "geo:"+lat+ ',' +lon;
88   }
89
90   private String getQueryField() {
91     String que = query.getText();
92     que = que.replace("&", "%26");
93     return que;
94   }
95
96   private String getLongitudeField() throws GeneratorException {
97     String lon = longitude.getText();
98     if (!lon.matches(LON_REGEXP)) {
99       throw new GeneratorException("Longitude is not a correct value.");
100     }
101     double val = Double.parseDouble(lon);
102     if (val < -180 || val > 180) {
103       throw new GeneratorException("Longitude must be in [-180:180]");
104     }
105     return lon;
106   }
107
108   private String getLatitudeField() throws GeneratorException {
109     String lat = latitude.getText();
110     if (!lat.matches(LAT_REGEXP)) {
111       throw new GeneratorException("Latitude is not a correct value.");
112     }
113     double val = Double.parseDouble(lat);
114     if (val < -90 || val > 90) {
115       throw new GeneratorException("Latitude must be in [-90:90]");
116     }
117     return lat;
118   }
119
120   public Grid getWidget() {
121     if (table != null) {
122       return table;
123     }
124     table = new Grid(7, 2);
125     
126     table.setText(0, 0, "Latitude");
127     table.setWidget(0, 1, latitude);
128     table.setText(1, 0, "Longitude");
129     table.setWidget(1, 1, longitude);
130     table.setText(2, 0, "Query");
131     table.setWidget(2, 1, query);
132     table.setText(3, 0, "OR");
133     table.setText(3, 1, "enter a Google Maps link and click Fill:");
134     // looks like this:
135     // http://maps.google.com/?ie=UTF8&ll=40.741404,-74.00322&spn=0.001484,0.003101&z=18
136     Button fill = new Button("Fill &uarr;");
137     fill.addClickListener(new ClickListener() {
138       public void onClick(Widget sender) {
139         fillWithMaps();
140       }
141     });
142     table.setWidget(4, 0, fill);
143     table.setWidget(4, 1, mapsLink);
144     
145     map.setSize("256px", "256px");
146     map.addControl(new SmallMapControl());
147     map.getElement().getStyle().setProperty("overflow", "hidden");
148     map.addMapClickHandler(new MapClickHandler() {
149       public void onClick(MapClickEvent event) {
150         mapClick(event);
151       }
152     });
153     table.setText(5, 0, "OR");
154     table.setText(5, 1, "use the map to select a location:");
155     SimplePanel sp = new SimplePanel();
156     sp.add(map);
157     table.setWidget(6, 1, sp);
158     return table;
159   }
160
161   protected void mapClick(MapClickEvent event) {
162     latitude.setText(String.valueOf(event.getLatLng().getLatitude()));
163     longitude.setText(String.valueOf(event.getLatLng().getLongitude()));
164     setMapMarker(event.getLatLng().getLatitude(), event.getLatLng().getLongitude(), false);
165     changeListener.onChange(latitude);
166     changeListener.onChange(longitude);
167   }
168   
169   protected void mapMarkerMoved() {
170     latitude.setText(String.valueOf(mapMarker.getLatLng().getLatitude()));
171     longitude.setText(String.valueOf(mapMarker.getLatLng().getLongitude()));
172     changeListener.onChange(latitude);
173     changeListener.onChange(longitude);
174   }
175   
176   protected void setMapMarker(double lat, double lon, boolean zoomAndCenter) {
177     if (mapMarker != null) {
178       map.removeOverlay(mapMarker);
179     }
180     LatLng ll = LatLng.newInstance(lat, lon);
181     if (zoomAndCenter) {
182       map.setCenter(ll);
183       map.setZoomLevel(12);
184     }
185     if (mapMarker != null) {
186       mapMarker.setLatLng(ll);
187     } else {
188       MarkerOptions opt = MarkerOptions.newInstance();
189       opt.setDraggable(true);
190       mapMarker = new Marker(ll, opt);
191       mapMarker.addMarkerDragEndHandler(new MarkerDragEndHandler() {
192         public void onDragEnd(MarkerDragEndEvent event) {
193           mapMarkerMoved();
194         }
195       });
196     }
197     map.addOverlay(mapMarker);  
198   }
199
200   protected void fillWithMaps() {
201     String link = mapsLink.getText();
202     if (!link.matches("http://maps.google.com/.*")) {
203       return;
204     }
205     String q = "";
206     if (link.matches(".*&q=[^&]*&.*")) {
207       StringBuilder qBuilder = new StringBuilder();
208       for (int i = link.indexOf("&q=") + 3;
209           i < link.length() && link.charAt(i) != '&'; ++i) {
210         qBuilder.append(link.charAt(i));
211       }
212       q = qBuilder.toString();
213       // special cases:
214       q = q.replace("+", " ");
215       q = q.replace("%26", "&");
216     }
217     
218     StringBuilder lat = new StringBuilder();
219     StringBuilder lon = new StringBuilder();
220     if (link.matches(".*&s?ll=[^&]*&.*")) {
221       int start;
222       if (link.indexOf("&sll=") == -1) {
223         start = link.indexOf("&ll=") + 4;
224       } else {
225         start = link.indexOf("&sll=") + 5;
226       }
227       boolean beforeComma = true;
228       for (int i = start; i < link.length() && link.charAt(i) != '&'; ++i) {
229         char c = link.charAt(i);
230         if (beforeComma) {
231           if (c == ',') {
232             beforeComma = false;
233           } else {
234             lat.append(c);
235           }
236         } else {
237           lon.append(c);
238         }
239       }
240     }
241     
242     query.setText(URL.decode(q));
243     latitude.setText(lat.toString());
244     longitude.setText(lon.toString());
245     changeListener.onChange(latitude);
246     changeListener.onChange(longitude);
247     this.onChange(latitude);
248   }
249
250   public void validate(Widget widget) throws GeneratorException {
251     if (widget == latitude) {
252       getLatitudeField();
253     }
254     if (widget == longitude) {
255       getLongitudeField();
256     }
257   }
258
259   public void setFocus() {
260     latitude.setFocus(true);
261   }
262
263   public void onChange(Widget sender) {
264     if (sender == latitude || sender == longitude) {
265       try {
266         double lat = Double.parseDouble(getLatitudeField());
267         double lon = Double.parseDouble(getLongitudeField());
268         setMapMarker(lat, lon, true);
269       } catch (NumberFormatException e) {
270       } catch (GeneratorException e) {
271       }
272     }
273   }
274 }