001: /*
002: * uDig - User Friendly Desktop Internet GIS client
003: * http://udig.refractions.net
004: * (C) 2004, Refractions Research Inc.
005: *
006: * This library is free software; you can redistribute it and/or
007: * modify it under the terms of the GNU Lesser General Public
008: * License as published by the Free Software Foundation;
009: * version 2.1 of the License.
010: *
011: * This library is distributed in the hope that it will be useful,
012: * but WITHOUT ANY WARRANTY; without even the implied warranty of
013: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
014: * Lesser General Public License for more details.
015: *
016: */
017: package net.refractions.udig.ui;
018:
019: import java.io.IOException;
020: import java.util.ArrayList;
021: import java.util.HashSet;
022: import java.util.Set;
023: import java.util.TreeSet;
024: import java.util.regex.Matcher;
025: import java.util.regex.Pattern;
026:
027: import net.refractions.udig.internal.ui.UiPlugin;
028: import net.refractions.udig.ui.internal.Messages;
029:
030: import org.eclipse.core.runtime.CoreException;
031: import org.eclipse.core.runtime.preferences.IExportedPreferences;
032: import org.eclipse.core.runtime.preferences.InstanceScope;
033: import org.eclipse.jface.viewers.ArrayContentProvider;
034: import org.eclipse.jface.viewers.DoubleClickEvent;
035: import org.eclipse.jface.viewers.IDoubleClickListener;
036: import org.eclipse.jface.viewers.ISelectionChangedListener;
037: import org.eclipse.jface.viewers.IStructuredSelection;
038: import org.eclipse.jface.viewers.LabelProvider;
039: import org.eclipse.jface.viewers.ListViewer;
040: import org.eclipse.jface.viewers.SelectionChangedEvent;
041: import org.eclipse.jface.viewers.StructuredSelection;
042: import org.eclipse.swt.SWT;
043: import org.eclipse.swt.events.ModifyEvent;
044: import org.eclipse.swt.events.ModifyListener;
045: import org.eclipse.swt.layout.GridData;
046: import org.eclipse.swt.layout.GridLayout;
047: import org.eclipse.swt.widgets.Composite;
048: import org.eclipse.swt.widgets.Control;
049: import org.eclipse.swt.widgets.Label;
050: import org.eclipse.swt.widgets.List;
051: import org.eclipse.swt.widgets.TabFolder;
052: import org.eclipse.swt.widgets.TabItem;
053: import org.eclipse.swt.widgets.Text;
054: import org.geotools.referencing.CRS;
055: import org.geotools.referencing.FactoryFinder;
056: import org.geotools.referencing.crs.DefaultGeographicCRS;
057: import org.opengis.metadata.Identifier;
058: import org.opengis.referencing.FactoryException;
059: import org.opengis.referencing.crs.CRSAuthorityFactory;
060: import org.opengis.referencing.crs.CoordinateReferenceSystem;
061: import org.osgi.service.prefs.BackingStoreException;
062: import org.osgi.service.prefs.Preferences;
063:
064: /**
065: * Creates a Control for choosing a Coordinate Reference System.
066: *
067: * @author jeichar
068: * @since 0.6.0
069: */
070: public class CRSChooser {
071:
072: private static final String WKT_ID = "WKT"; //$NON-NLS-1$
073: private static final String ALIASES_ID = "ALIASES"; //$NON-NLS-1$
074: private static final String LAST_ID = "LAST_ID"; //$NON-NLS-1$
075: private static final String NAME_ID = "NAME_ID"; //$NON-NLS-1$
076: private static final String CUSTOM_ID = "CRS.Custom.Services"; //$NON-NLS-1$
077: private static final Controller DEFAULT = new Controller() {
078:
079: public void handleClose() {
080: }
081:
082: public void handleOk() {
083: }
084:
085: };
086:
087: ListViewer codesList;
088: Text searchText;
089: Text wktText;
090: Text keywordsText;
091: CoordinateReferenceSystem selectedCRS;
092: Matcher matcher;
093: private TabFolder folder;
094: private Controller parentPage;
095:
096: public CRSChooser(Controller parentPage) {
097: matcher = Pattern.compile(".*?\\(([^(]*)\\)$").matcher(""); //$NON-NLS-1$ //$NON-NLS-2$
098: this .parentPage = parentPage;
099: }
100:
101: public CRSChooser() {
102: this (DEFAULT);
103: }
104:
105: private Control createCustomCRSControl(Composite parent) {
106: Composite composite = new Composite(parent, SWT.NONE);
107:
108: GridLayout layout = new GridLayout(2, false);
109: composite.setLayout(layout);
110:
111: GridData gridData = new GridData();
112: Label keywordsLabel = new Label(composite, SWT.NONE);
113: keywordsLabel.setText(Messages.CRSChooser_keywordsLabel);
114: keywordsLabel.setLayoutData(gridData);
115: keywordsLabel.setToolTipText(Messages.CRSChooser_tooltip);
116:
117: gridData = new GridData(SWT.FILL, SWT.NONE, true, false);
118: keywordsText = new Text(composite, SWT.SINGLE | SWT.BORDER);
119: keywordsText.setLayoutData(gridData);
120: keywordsText.setToolTipText(Messages.CRSChooser_tooltip);
121:
122: gridData = new GridData(SWT.FILL, SWT.NONE, true, false);
123: gridData.horizontalSpan = 2;
124: Label editorLabel = new Label(composite, SWT.NONE);
125: editorLabel.setText(Messages.CRSChooser_label_crsWKT);
126: editorLabel.setLayoutData(gridData);
127:
128: gridData = new GridData(SWT.FILL, SWT.FILL, true, true);
129: gridData.horizontalSpan = 2;
130: wktText = new Text(composite, SWT.MULTI | SWT.BORDER
131: | SWT.V_SCROLL | SWT.H_SCROLL);
132: if (selectedCRS != null)
133: wktText.setText(selectedCRS.toWKT());
134: wktText.setLayoutData(gridData);
135: wktText.addModifyListener(new ModifyListener() {
136:
137: public void modifyText(ModifyEvent e) {
138: if (!keywordsText.isEnabled())
139: keywordsText.setEnabled(true);
140: }
141:
142: });
143: return composite;
144: }
145:
146: private Control createStandardCRSControl(Composite parent) {
147: Composite composite = new Composite(parent, SWT.NONE);
148: GridLayout layout = new GridLayout();
149: composite.setLayout(layout);
150:
151: GridData gridData = new GridData();
152: Label codesLabel = new Label(composite, SWT.NONE);
153: codesLabel.setText(Messages.CRSChooser_label_crs);
154: codesLabel.setLayoutData(gridData);
155:
156: gridData = new GridData(SWT.FILL, SWT.FILL, false, false);
157: searchText = new Text(composite, SWT.SINGLE | SWT.BORDER);
158: searchText.setLayoutData(gridData);
159: searchText.addModifyListener(new ModifyListener() {
160: public void modifyText(ModifyEvent e) {
161: fillCodesList();
162: }
163: });
164:
165: gridData = new GridData(400, 300);
166: codesList = new ListViewer(composite);
167: codesList.setContentProvider(new ArrayContentProvider());
168: codesList.setLabelProvider(new LabelProvider());
169: codesList
170: .addSelectionChangedListener(new ISelectionChangedListener() {
171:
172: public void selectionChanged(
173: SelectionChangedEvent event) {
174: selectedCRS = null;
175: String crsCode = (String) ((IStructuredSelection) codesList
176: .getSelection()).getFirstElement();
177: if (crsCode == null)
178: return;
179: matcher.reset(crsCode);
180: if (matcher.matches()) {
181: selectedCRS = createCRS(matcher.group(1));
182: if (selectedCRS != null && wktText != null) {
183: wktText.setText(selectedCRS.toWKT());
184: Preferences node = findNode(matcher
185: .group(1));
186: if (node != null) {
187: Preferences kn = node
188: .node(ALIASES_ID);
189: try {
190: String[] keywords = kn.keys();
191: if (keywords.length > 0) {
192: StringBuffer buffer = new StringBuffer();
193: for (String string : keywords) {
194: buffer.append(", "); //$NON-NLS-1$
195: buffer.append(string);
196: }
197: buffer.delete(0, 2);
198: keywordsText.setText(buffer
199: .toString());
200: }
201: } catch (BackingStoreException e) {
202: UiPlugin.log("", e); //$NON-NLS-1$
203: }
204:
205: } else {
206: keywordsText.setText(""); //$NON-NLS-1$
207: }
208: }
209: }
210:
211: }
212:
213: });
214:
215: codesList.addDoubleClickListener(new IDoubleClickListener() {
216:
217: public void doubleClick(DoubleClickEvent event) {
218: parentPage.handleOk();
219: parentPage.handleClose();
220:
221: }
222:
223: });
224:
225: codesList.getControl().setLayoutData(gridData);
226: /*
227: * fillCodesList() by itself resizes the Preferences Page but in the paintlistener it
228: * flickers the window
229: */
230: fillCodesList();
231:
232: return composite;
233: }
234:
235: /**
236: * Creates the CRS PreferencePage root control with a CRS already selected
237: *
238: * @param parent PreferencePage for this chooser
239: * @param crs current CRS for the associated map
240: * @return control for the PreferencePage
241: */
242: public Control createControl(Composite parent,
243: CoordinateReferenceSystem crs) {
244: Control control = createControl(parent);
245: selectedCRS = crs;
246: gotoCRS(selectedCRS);
247: return control;
248: }
249:
250: public void clearSearch() {
251: searchText.setText(""); //$NON-NLS-1$
252: }
253:
254: /**
255: * Takes in a CRS, finds it in the list and highlights it
256: *
257: * @param crs
258: */
259: @SuppressWarnings("unchecked")
260: public void gotoCRS(CoordinateReferenceSystem crs) {
261: if (crs != null) {
262: final List list = codesList.getList();
263: Set<Identifier> identifiers = new HashSet<Identifier>(crs
264: .getIdentifiers());
265: identifiers.add(crs.getName());
266:
267: final Set<Integer> candidates = new HashSet<Integer>();
268:
269: for (int i = 0; i < list.getItemCount(); i++) {
270: for (Identifier identifier : identifiers) {
271: final String item = list.getItem(i);
272: if (sameEPSG(crs, identifier, item)
273: || exactMatch(crs, identifier, item)) {
274: codesList.setSelection(new StructuredSelection(
275: item), false);
276: list.setTopIndex(i);
277: return;
278: }
279: if (isMatch(crs, identifier, item)) {
280: candidates.add(i);
281: }
282: }
283: }
284: if (candidates.isEmpty()) {
285: java.util.List<String> input = (java.util.List<String>) codesList
286: .getInput();
287: input.add(0, crs.getName().toString());
288: codesList.setInput(input);
289: codesList.setSelection(new StructuredSelection(crs
290: .getName().toString()), false);
291: list.setTopIndex(0);
292: try {
293: String toWKT = crs.toWKT();
294: wktText.setText(toWKT);
295: } catch (RuntimeException e) {
296: UiPlugin.log(crs.toString()
297: + " cannot be formatted as WKT", e); //$NON-NLS-1$
298: wktText.setText(Messages.CRSChooser_unknownWKT);
299: }
300: } else {
301: Integer next = candidates.iterator().next();
302: codesList.setSelection(new StructuredSelection(list
303: .getItem(next)), false);
304: list.setTopIndex(next);
305:
306: }
307:
308: }
309: }
310:
311: private boolean exactMatch(CoordinateReferenceSystem crs,
312: Identifier identifier, String item) {
313: return (crs == DefaultGeographicCRS.WGS84 && item
314: .contains("EPSG:4326")) || item.equalsIgnoreCase(identifier.toString()); //$NON-NLS-1$
315: }
316:
317: private boolean sameEPSG(CoordinateReferenceSystem crs,
318: Identifier identifier, String item) {
319: String toString = identifier.toString();
320: return toString.contains("EPSG:") && item.contains(toString); //$NON-NLS-1$
321: }
322:
323: private boolean isMatch(CoordinateReferenceSystem crs,
324: Identifier identifier, String item) {
325: return (crs == DefaultGeographicCRS.WGS84 && item
326: .contains("4326")) || item.contains(identifier.toString()); //$NON-NLS-1$
327: }
328:
329: /**
330: * Creates the CRS PreferencePage root control with no CRS selected
331: *
332: * @param parent PreferencePage for this chooser
333: * @return control for the PreferencePage
334: */
335: public Control createControl(Composite parent) {
336: GridData gridData = null;
337:
338: gridData = new GridData(SWT.FILL, SWT.FILL, true, true);
339: folder = new TabFolder(parent, SWT.NONE);
340: folder.setLayoutData(gridData);
341:
342: TabItem standard = new TabItem(folder, SWT.NONE);
343: standard.setText(Messages.CRSChooser_tab_standardCRS);
344: Control stdCRS = createStandardCRSControl(folder);
345: standard.setControl(stdCRS);
346:
347: TabItem custom = new TabItem(folder, SWT.NONE);
348: custom.setText(Messages.CRSChooser_tab_customCRS);
349: Control cstCRS = createCustomCRSControl(folder);
350: custom.setControl(cstCRS);
351:
352: return folder;
353: }
354:
355: /**
356: * checks if all keywords in filter array are in input
357: *
358: * @param input test string
359: * @param filter array of keywords
360: * @return true, if all keywords in filter are in the input, false otherwise
361: */
362: protected boolean matchesFilter(String input, String[] filter) {
363: for (String match : filter) {
364: if (!input.contains(match))
365: return false;
366: }
367: return true;
368: }
369:
370: /**
371: * filters all CRS Names from all available CRS authorities
372: *
373: * @param filter array of keywords
374: * @return Set of CRS Names which contain all the filter keywords
375: */
376: protected Set<String> filterCRSNames(String[] filter) {
377: Set<String> descriptions = new TreeSet<String>();
378: for (Object object : FactoryFinder.getCRSAuthorityFactories()) {
379: CRSAuthorityFactory factory = (CRSAuthorityFactory) object;
380:
381: try {
382: Set<String> codes = factory
383: .getAuthorityCodes(CoordinateReferenceSystem.class);
384: for (Object codeObj : codes) {
385: String code = (String) codeObj;
386: String description;
387: try {
388: description = factory.getDescriptionText(code)
389: .toString();
390: } catch (Exception e1) {
391: description = Messages.CRSChooser_unnamed;
392: }
393: description += " (" + code + ")"; //$NON-NLS-1$ //$NON-NLS-2$
394: if (matchesFilter(description.toUpperCase(), filter))
395: descriptions.add(description);
396: }
397: } catch (FactoryException e) {
398: // list = null;
399: }
400: }
401: return descriptions;
402: }
403:
404: /**
405: * populates the codes list with a filtered list of CRS names
406: */
407: protected void fillCodesList() {
408: String[] searchParms = searchText.getText().toUpperCase()
409: .split(" "); //$NON-NLS-1$
410: Set<String> descriptions = filterCRSNames(searchParms);
411: descriptions = filterCustomCRSs(descriptions, searchParms);
412: java.util.List<String> list = new ArrayList<String>(
413: descriptions);
414: codesList.setInput(list);
415: if (list != null && !list.isEmpty()) {
416: codesList
417: .setSelection(new StructuredSelection(list.get(0)));
418: } else {
419: codesList.setSelection(new StructuredSelection());
420: // System.out.println( "skipped");
421: }
422: }
423:
424: private Set<String> filterCustomCRSs(Set<String> descriptions,
425: String[] searchParms) {
426: try {
427: Preferences root = UiPlugin.getUserPreferences();
428: Preferences node = root.node(InstanceScope.SCOPE).node(
429: CUSTOM_ID);
430:
431: for (String id : node.childrenNames()) {
432: Preferences child = node.node(id);
433: String string = child.get(NAME_ID, null);
434: if (string != null
435: && matchesFilter(string.toUpperCase(),
436: searchParms)) {
437: descriptions.add(string);
438: continue;
439: }
440:
441: Preferences aliases = child.node(ALIASES_ID);
442: for (String alias : aliases.keys()) {
443: if (matchesFilter(alias.toUpperCase(), searchParms)) {
444: descriptions.add(string);
445: continue;
446: }
447: }
448: }
449: } catch (Exception e) {
450: UiPlugin.log("", e); //$NON-NLS-1$
451: }
452: return descriptions;
453: }
454:
455: /**
456: * creates a CRS from a code when the appropriate CRSAuthorityFactory is unknown
457: *
458: * @param code CRS code
459: * @return CRS object from appropriate authority, or null if the appropriate factory cannot be
460: * determined
461: */
462: protected CoordinateReferenceSystem createCRS(String code) {
463: if (code == null)
464: return null;
465: for (Object object : FactoryFinder.getCRSAuthorityFactories()) {
466: CRSAuthorityFactory factory = (CRSAuthorityFactory) object;
467: try {
468: return (CoordinateReferenceSystem) factory
469: .createObject(code);
470: } catch (FactoryException e2) {
471: // then we have the wrong factory
472: // is there a better way to do this?
473: }
474: }
475: try {
476: Preferences child = findNode(code);
477: if (child != null) {
478: String wkt = child.get(WKT_ID, null);
479: if (wkt != null) {
480: try {
481: return FactoryFinder.getCRSFactory(null)
482: .createFromWKT(wkt);
483: } catch (Exception e) {
484: UiPlugin.log(wkt, e);
485: child.removeNode();
486: }
487: }
488: }
489:
490: } catch (Exception e) {
491: UiPlugin.log(null, e);
492: }
493: return null; // should throw an exception?
494: }
495:
496: private Preferences findNode(String code) {
497: try {
498: Preferences root = UiPlugin.getUserPreferences();
499: Preferences node = root.node(InstanceScope.SCOPE).node(
500: CUSTOM_ID);
501:
502: if (node.nodeExists(code)) {
503: return node.node(code);
504: }
505:
506: for (String id : node.childrenNames()) {
507: Preferences child = node.node(id);
508: String name = child.get(NAME_ID, null);
509: if (name != null
510: && matchesFilter(name, new String[] { code })) {
511: return child;
512: }
513: }
514: return null;
515: } catch (BackingStoreException e) {
516: UiPlugin.log("Error loading", e);//$NON-NLS-1$
517: return null;
518: }
519: }
520:
521: /**
522: * returns the selected CRS
523: *
524: * @return selected CRS
525: */
526: public CoordinateReferenceSystem getCRS() {
527: if (folder == null)
528: return selectedCRS;
529: if (folder.getSelectionIndex() == 1) {
530: try {
531: String text = wktText.getText();
532: CoordinateReferenceSystem createdCRS = FactoryFinder
533: .getCRSFactory(null).createFromWKT(text);
534:
535: if (keywordsText.getText().trim().length() > 0) {
536: Preferences node = findNode(createdCRS.getName()
537: .getCode());
538: if (node != null) {
539: Preferences kn = node.node(ALIASES_ID);
540: String[] keywords = keywordsText.getText()
541: .split(","); //$NON-NLS-1$
542: kn.clear();
543: for (String string : keywords) {
544: string = string.trim().toUpperCase();
545: if (string.length() > 0)
546: kn.put(string, string);
547: }
548: kn.flush();
549: } else {
550: CoordinateReferenceSystem found = createCRS(createdCRS
551: .getName().getCode());
552: if (found != null
553: && CRS.findMathTransform(found,
554: createdCRS, true).isIdentity()) {
555: saveKeywords(found);
556: return found;
557: }
558:
559: Set<Identifier> identifiers = new HashSet<Identifier>(
560: createdCRS.getIdentifiers());
561: for (Identifier identifier : identifiers) {
562: found = createCRS(identifier.toString());
563: if (found != null
564: && CRS.findMathTransform(found,
565: createdCRS, true)
566: .isIdentity()) {
567: saveKeywords(found);
568: return found;
569: }
570: }
571: return saveCustomizedCRS(text, true, createdCRS);
572: }
573: }
574:
575: return createdCRS;
576: } catch (Exception e) {
577: UiPlugin.log("", e); //$NON-NLS-1$
578: }
579: }
580: if (selectedCRS == null) {
581: return createCRS(searchText.getText());
582: }
583: return selectedCRS;
584: }
585:
586: /**
587: *
588: * @param found
589: * @throws CoreException
590: * @throws IOException
591: * @throws BackingStoreException
592: */
593: private void saveKeywords(CoordinateReferenceSystem found)
594: throws CoreException, IOException, BackingStoreException {
595: String[] keywords = keywordsText.getText().split(","); //$NON-NLS-1$
596: if (keywords.length > 0) {
597: boolean legalKeyword = false;
598: // determine whether there are any keywords that are not blank.
599: for (int i = 0; i < keywords.length; i++) {
600: String string = keywords[i];
601: string = string.trim().toUpperCase();
602: if (string.length() > 0) {
603: legalKeyword = true;
604: break;
605: }
606: }
607: if (legalKeyword) {
608: saveCustomizedCRS(found.toWKT(), false, found);
609: }
610: }
611: keywordsText.setText(""); //$NON-NLS-1$
612: wktText.setText(found.toWKT());
613: }
614:
615: /**
616: * @param text
617: * @param createdCRS
618: * @throws CoreException
619: * @throws IOException
620: * @throws BackingStoreException
621: */
622: private CoordinateReferenceSystem saveCustomizedCRS(String text,
623: boolean processWKT, CoordinateReferenceSystem createdCRS)
624: throws CoreException, IOException, BackingStoreException {
625: Preferences root = UiPlugin.getUserPreferences();
626: Preferences node = root.node(InstanceScope.SCOPE).node(
627: CUSTOM_ID);
628: int lastID;
629: String code;
630: String name;
631: String newWKT;
632: if (processWKT) {
633: lastID = Integer.parseInt(node.get(LAST_ID, "0")); //$NON-NLS-1$
634: code = "UDIG:" + lastID; //$NON-NLS-1$
635: name = createdCRS.getName().toString() + "(" + code + ")";//$NON-NLS-1$ //$NON-NLS-2$
636: lastID++;
637: node.putInt(LAST_ID, lastID);
638: newWKT = processingWKT(text, lastID);
639: } else {
640: Set<Identifier> ids = createdCRS.getIdentifiers();
641: if (!ids.isEmpty()) {
642: Identifier id = ids.iterator().next();
643: code = id.toString();
644: name = createdCRS.getName().getCode()
645: + " (" + code + ")"; //$NON-NLS-1$ //$NON-NLS-2$
646: } else {
647: name = code = createdCRS.getName().getCode();
648: }
649:
650: newWKT = text;
651: }
652:
653: Preferences child = node.node(code);
654: child.put(NAME_ID, name);
655: child.put(WKT_ID, newWKT);
656: String[] keywords = keywordsText.getText().split(","); //$NON-NLS-1$
657: if (keywords.length > 0) {
658: Preferences keyworkNode = child.node(ALIASES_ID);
659: for (String string : keywords) {
660: string = string.trim().toUpperCase();
661: keyworkNode.put(string, string);
662: }
663: }
664: node.flush();
665:
666: return createdCRS;
667: }
668:
669: /**
670: * Remove the last AUTHORITY if it exists and add a UDIG Authority
671: */
672: private String processingWKT(String text, int lastID) {
673: String newWKT;
674: String[] prep = text.split(","); //$NON-NLS-1$
675: if (prep[prep.length - 2].toUpperCase().contains("AUTHORITY")) { //$NON-NLS-1$
676: String substring = text.substring(0, text.lastIndexOf(','));
677: newWKT = substring.substring(0, substring.lastIndexOf(','))
678: + ", AUTHORITY[\"UDIG\",\"" + (lastID - 1) + "\"]]"; //$NON-NLS-1$ //$NON-NLS-2$
679: } else {
680: newWKT = text.substring(0, text.lastIndexOf(']'))
681: + ", AUTHORITY[\"UDIG\",\"" + (lastID - 1) + "\"]]"; //$NON-NLS-1$ //$NON-NLS-2$
682: }
683: wktText.setText(newWKT);
684: return newWKT;
685: }
686:
687: public void setController(Controller controller) {
688: parentPage = controller;
689: }
690:
691: }
|