3 | Zotero Maps
4 |
6 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
--------------------------------------------------------------------------------
/chrome/content/zotero-maps/overlay.xul:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
6 |
7 |
8 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
--------------------------------------------------------------------------------
/chrome/skin/default/zotero-maps/zotero.cfg:
--------------------------------------------------------------------------------
1 | [first]
2 | OpenLayers/SingleFile.js
3 | OpenLayers.js
4 | OpenLayers/BaseTypes.js
5 | OpenLayers/BaseTypes/Class.js
6 | OpenLayers/Util.js
7 | Rico/Corner.js
8 | Rico/Color.js
9 |
10 | [last]
11 |
12 | [include]
13 | OpenLayers/Ajax.js
14 | OpenLayers/Events.js
15 | OpenLayers/Map.js
16 | OpenLayers/Projection.js
17 | OpenLayers/Layer.js
18 | OpenLayers/Layer/Grid.js
19 | OpenLayers/Layer/HTTPRequest.js
20 | OpenLayers/Layer/WMS.js
21 | OpenLayers/Layer/TMS.js
22 | OpenLayers/Layer/Markers.js
23 | OpenLayers/Layer/SphericalMercator.js
24 | OpenLayers/Popup/FramedCloud.js
25 | OpenLayers/Feature.js
26 | OpenLayers/Tile.js
27 | OpenLayers/Tile/Image.js
28 | OpenLayers/Control/Navigation.js
29 | OpenLayers/Control/PanZoomBar.js
30 | OpenLayers/Control/Permalink.js
31 | OpenLayers/Control/ArgParser.js
32 | OpenLayers/Control/Attribution.js
33 | OpenLayers/Control/KeyboardDefaults.js
34 | OpenLayers/Format/JSON.js
35 |
36 | [exclude]
37 |
--------------------------------------------------------------------------------
/zotero.cfg:
--------------------------------------------------------------------------------
1 | # This file contains the build recipe for the single-file version
2 | # of OpenLayers shipped with Zotero Maps.
3 |
4 | [first]
5 | OpenLayers/SingleFile.js
6 | OpenLayers.js
7 | OpenLayers/BaseTypes.js
8 | OpenLayers/BaseTypes/Class.js
9 | OpenLayers/Util.js
10 | Rico/Corner.js
11 | Rico/Color.js
12 |
13 | [last]
14 |
15 | [include]
16 | OpenLayers/Ajax.js
17 | OpenLayers/Events.js
18 | OpenLayers/Map.js
19 | OpenLayers/Projection.js
20 | OpenLayers/Layer.js
21 | OpenLayers/Layer/Grid.js
22 | OpenLayers/Layer/HTTPRequest.js
23 | OpenLayers/Layer/WMS.js
24 | OpenLayers/Layer/TMS.js
25 | OpenLayers/Layer/Markers.js
26 | OpenLayers/Layer/SphericalMercator.js
27 | OpenLayers/Popup/FramedCloud.js
28 | OpenLayers/Feature.js
29 | OpenLayers/Tile.js
30 | OpenLayers/Tile/Image.js
31 | OpenLayers/Control/Navigation.js
32 | OpenLayers/Control/PanZoomBar.js
33 | OpenLayers/Control/Permalink.js
34 | OpenLayers/Control/ArgParser.js
35 | OpenLayers/Control/Attribution.js
36 | OpenLayers/Control/KeyboardDefaults.js
37 | OpenLayers/Format/JSON.js
38 |
39 | [exclude]
40 |
--------------------------------------------------------------------------------
/install.rdf:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
6 | zoteromaps@entropyfree.com
7 | Zotero Maps plugin
8 | 1.0.7
9 | A mapping tool for Zotero collections, based on OpenLayers.
10 | Entropy Free LLC
11 | https://www.zotero.org/download/plugins.rdf
12 | chrome://zotero-maps/content/about.xul
13 | chrome://zotero-maps/skin/zotero_z_32px.png
14 | 2
15 |
16 |
17 |
18 |
19 | zotero@chnm.gmu.edu
20 | Zotero
21 | http://www.zotero.org
22 | 1.0.3
23 | 2.*
24 |
25 |
26 |
27 |
28 |
29 |
30 | {ec8030f7-c20a-464f-9b0e-13a3a9e97384}
31 | 2.0.*
32 | 3.*
33 |
34 |
35 |
36 |
37 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | This Educational Community License (the "License") applies
2 | to any original work of authorship (the "Original Work") whose owner
3 | (the "Licensor") has placed the following notice immediately following
4 | the copyright notice for the Original Work:
5 |
6 | Copyright (c) 2008-2009 Entropy Free LLC
7 |
8 | Licensed under the Educational Community License version 1.0
9 |
10 | This Original Work, including software, source code, documents,
11 | or other related items, is being provided by the copyright holder(s)
12 | subject to the terms of the Educational Community License. By
13 | obtaining, using and/or copying this Original Work, you agree that you
14 | have read, understand, and will comply with the following terms and
15 | conditions of the Educational Community License:
16 |
17 | Permission to use, copy, modify, merge, publish, distribute, and
18 | sublicense this Original Work and its documentation, with or without
19 | modification, for any purpose, and without fee or royalty to the
20 | copyright holder(s) is hereby granted, provided that you include the
21 | following on ALL copies of the Original Work or portions thereof,
22 | including modifications or derivatives, that you make:
23 |
24 | The full text of the Educational Community License in a location viewable to
25 | users of the redistributed or derivative work.
26 |
27 | Any pre-existing intellectual property disclaimers, notices, or terms and
28 | conditions.
29 |
30 | Notice of any changes or modifications to the Original Work, including the
31 | date the changes were made.
32 |
33 | Any modifications of the Original Work must be distributed in such a manner as
34 | to avoid any confusion with the Original Work of the copyright holders.
35 |
36 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
37 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
38 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
39 | IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
40 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
41 | TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
42 | SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
43 |
44 | The name and trademarks of copyright holder(s) may NOT be used
45 | in advertising or publicity pertaining to the Original or Derivative
46 | Works without specific, written prior permission. Title to copyright in
47 | the Original Work and any associated documentation will at all times
48 | remain with the copyright holders.
49 |
--------------------------------------------------------------------------------
/chrome/content/zotero-maps/setup.js:
--------------------------------------------------------------------------------
1 | Zotero.Maps = {
2 | SCHEME: "zotero://maps",
3 |
4 | channel: {
5 | CONTENT_URI: "chrome://zotero-maps/content/ui.html",
6 | newChannel: function (uri) {
7 | var ioService = Components.classes["@mozilla.org/network/io-service;1"]
8 | .getService(Components.interfaces.nsIIOService);
9 | try {
10 | var ext_uri = ioService.newURI(this.CONTENT_URI, null, null);
11 | var extChannel = ioService.newChannelFromURI(ext_uri);
12 | return extChannel;
13 | }
14 | catch (e){
15 | Zotero.debug(e);
16 | throw (e);
17 | }
18 | }
19 | },
20 |
21 | loaded: false,
22 |
23 | DB: null,
24 |
25 | init: function () {
26 | var protocol = Components.classes["@mozilla.org/network/protocol;1?name=zotero"]
27 | .getService(Components.interfaces.nsIProtocolHandler)
28 | .wrappedJSObject;
29 | protocol._extensions[this.SCHEME] = this.channel;
30 |
31 | this.DB = new Zotero.DBConnection('zotero-maps');
32 |
33 | if (!this.DB.tableExists('cache')) {
34 | this.DB.query("CREATE TABLE cache " +
35 | " (lng NUMERIC, lat NUMERIC, name VARCHAR(255))");
36 | }
37 |
38 | // Register the callback in Zotero as an item observer
39 | var notifierID = Zotero.Notifier.registerObserver(this.notifierCallback, ['item']);
40 |
41 | // Unregister callback when the window closes (important to avoid a memory leak)
42 | window.addEventListener('unload', function(e) {
43 | Zotero.Notifier.unregisterObserver(notifierID); }, false);
44 | },
45 |
46 | get: function (loc) {
47 | var place = loc.replace("\"", "\\\"");
48 | return this.DB.rowQuery("SELECT * FROM cache WHERE name = \"" + place + "\"");
49 | },
50 |
51 | set: function (loc, lng, lat) {
52 | if (!this.get(loc)) {
53 | /* this is workaround for an apparent bug where passing a
54 | * float to a placeholder casts it to an integer?? */
55 | var place = loc.replace("\"", "\\\"");
56 | this.DB.query("INSERT INTO cache (lng,lat,name) VALUES " +
57 | "(" + lng + "," + lat + ", \"" + place + "\")");
58 | }
59 | },
60 |
61 | query: function (item, placename, callback) {
62 | var url='http://zotero.ws.geonames.org/search';
63 | var q = '?q='+placename+'&maxRows=1&type=json';
64 | OpenLayers.loadURL(url, q, item, function (req) {
65 | Zotero.Maps.query_callback(req, item, placename, callback) });
66 | },
67 |
68 | json_format: new OpenLayers.Format.JSON(),
69 |
70 | query_callback: function (req, item, placename, callback) {
71 | try {
72 | // TODO: give a nice message if the webservice is down (timeouts are too long)
73 | var json = Zotero.Maps.json_format.read(req.responseText);
74 | var geoname;
75 | if(json && json.totalResultsCount > 0) {
76 | geoname = json.geonames[0];
77 | /* Store the geonames result in the cache for later reuse.
78 | * The geonames service may return a different place name than
79 | * the one given; if so, cache that, too. */
80 | this.set(placename, geoname.lng, geoname.lat);
81 | if (geoname.name != placename) {
82 | this.set(geoname.name, geoname.lng, geoname.lat);
83 | }
84 | if (callback != null){
85 | callback(item, geoname);
86 | }
87 | } else {
88 | /* Cache the place name as unknown so that we don't
89 | * keep hammering the geocoder. */
90 | this.set(placename, 0.0, 0.0);
91 | geoname = "UNKNOWN";
92 | callback(item, geoname);
93 | }
94 | } catch (e) {
95 | alert(e);
96 | }
97 | },
98 |
99 | // Callback implementing the notify() method to pass to the Notifier
100 | notifierCallback: {
101 | notify: function(event, type, ids, extraData) {
102 | if (event == 'add' || event == 'modify') {
103 | // Retrieve the added/modified items as Item objects
104 | var items = Zotero.Items.get(ids);
105 | for each(var item in items) {
106 | var placename = item.getField("place");
107 | if (placename && !Zotero.Maps.get(placename)) {
108 | Zotero.Maps.query(item, placename);
109 | }
110 | }
111 | }
112 | }
113 | },
114 |
115 | load: function() {
116 | var uri = "";
117 | var id = ZoteroPane.getSelectedItems(true);
118 | if (id != "") {
119 | uri = this.SCHEME + '/selection/'
120 | } else{
121 | id = ZoteroPane.getSelectedCollection(true);
122 | if (id) {
123 | uri = this.SCHEME + '/collection/' + id;
124 | } else {
125 | id = ZoteroPane.getSelectedSavedSearch(true);
126 | if (id) {
127 | uri = this.SCHEME + '/search/' + id;
128 | } else {
129 | uri = this.SCHEME + '/library/';
130 | }
131 | }
132 | }
133 | if (uri) {
134 | window.loadURI(uri);
135 | } else {
136 | // var str = document.getElementById('zotero-maps-strings')
137 | // .getFormattedString("zoteromaps.noneSelected");
138 | var str = "No collection is selected.";
139 | alert(str);
140 | }
141 | },
142 |
143 | selection: function () {
144 | return ZoteroPane.getSelectedItems(true);
145 | }
146 | };
147 |
148 | window.addEventListener('load', function(e) { Zotero.Maps.init(); }, false);
149 |
--------------------------------------------------------------------------------
/chrome/skin/default/zotero-maps/theme/default/style.css:
--------------------------------------------------------------------------------
1 | div.olLayerDiv {
2 | -moz-user-select: none
3 | }
4 |
5 | .olLayerGoogleCopyright {
6 | left: 2px;
7 | bottom: 2px;
8 | }
9 | .olLayerGooglePoweredBy {
10 | left: 2px;
11 | bottom: 15px;
12 | }
13 | .olControlAttribution {
14 | font-size: smaller;
15 | right: 3px;
16 | bottom: 4.5em;
17 | position: absolute;
18 | display: block;
19 | }
20 | .olControlScale {
21 | right: 3px;
22 | bottom: 3em;
23 | display: block;
24 | position: absolute;
25 | font-size: smaller;
26 | }
27 | .olControlScaleLine {
28 | left: 10px;
29 | bottom: 15px;
30 | font-size: xx-small;
31 | }
32 | .olControlScaleLineBottom {
33 | border: solid 2px black;
34 | border-bottom: none;
35 | margin-top:-2px;
36 | text-align: center;
37 | }
38 | .olControlScaleLineTop {
39 | border: solid 2px black;
40 | border-top: none;
41 | text-align: center;
42 | }
43 |
44 | .olControlPermalink {
45 | right: 3px;
46 | bottom: 1.5em;
47 | display: block;
48 | position: absolute;
49 | font-size: smaller;
50 | }
51 |
52 | div.olControlMousePosition {
53 | bottom: 0em;
54 | right: 3px;
55 | display: block;
56 | position: absolute;
57 | font-family: Arial;
58 | font-size: smaller;
59 | }
60 |
61 | .olControlOverviewMapContainer {
62 | position: absolute;
63 | bottom: 0px;
64 | right: 0px;
65 | }
66 |
67 | .olControlOverviewMapElement {
68 | padding: 10px 18px 10px 10px;
69 | background-color: #00008B;
70 | -moz-border-radius: 1em 0 0 0;
71 | }
72 |
73 | .olControlOverviewMapMinimizeButton {
74 | right: 0px;
75 | bottom: 80px;
76 | }
77 |
78 | .olControlOverviewMapMaximizeButton {
79 | right: 0px;
80 | bottom: 80px;
81 | }
82 |
83 | .olControlOverviewMapExtentRectangle {
84 | overflow: hidden;
85 | background-image: url("img/blank.gif");
86 | cursor: move;
87 | border: 2px dotted red;
88 | }
89 | .olControlOverviewMapRectReplacement {
90 | overflow: hidden;
91 | cursor: move;
92 | background-image: url("img/overview_replacement.gif");
93 | background-repeat: no-repeat;
94 | background-position: center;
95 | }
96 |
97 | .olLayerGeoRSSDescription {
98 | float:left;
99 | width:100%;
100 | overflow:auto;
101 | font-size:1.0em;
102 | }
103 | .olLayerGeoRSSClose {
104 | float:right;
105 | color:gray;
106 | font-size:1.2em;
107 | margin-right:6px;
108 | font-family:sans-serif;
109 | }
110 | .olLayerGeoRSSTitle {
111 | float:left;font-size:1.2em;
112 | }
113 |
114 | .olPopupContent {
115 | padding:5px;
116 | overflow: auto;
117 | }
118 | .olControlNavToolbar {
119 | width:0px;
120 | height:0px;
121 | }
122 | .olControlNavToolbar div {
123 | display:block;
124 | width: 28px;
125 | height: 28px;
126 | top: 300px;
127 | left: 6px;
128 | position: relative;
129 | }
130 |
131 | .olControlNavigationHistoryPreviousItemActive {
132 | background-image: url("img/view_previous_on.png");
133 | background-repeat: no-repeat;
134 | width: 24px;
135 | height: 24px;
136 | }
137 | .olControlNavigationHistoryPreviousItemInactive {
138 | background-image: url("img/view_previous_off.png");
139 | background-repeat: no-repeat;
140 | width: 24px;
141 | height: 24px;
142 | }
143 | .olControlNavigationHistoryNextItemActive {
144 | background-image: url("img/view_next_on.png");
145 | background-repeat: no-repeat;
146 | width: 24px;
147 | height: 24px;
148 | }
149 | .olControlNavigationHistoryNextItemInactive {
150 | background-image: url("img/view_next_off.png");
151 | background-repeat: no-repeat;
152 | width: 24px;
153 | height: 24px;
154 | }
155 |
156 | .olControlNavToolbar .olControlNavigationItemActive {
157 | background-image: url("img/panning-hand-on.png");
158 | background-repeat: no-repeat;
159 | }
160 | .olControlNavToolbar .olControlNavigationItemInactive {
161 | background-image: url("img/panning-hand-off.png");
162 | background-repeat: no-repeat;
163 | }
164 | .olControlNavToolbar .olControlZoomBoxItemActive {
165 | background-image: url("img/drag-rectangle-on.png");
166 | background-color: orange;
167 | background-repeat: no-repeat;
168 | }
169 | .olControlNavToolbar .olControlZoomBoxItemInactive {
170 | background-image: url("img/drag-rectangle-off.png");
171 | background-repeat: no-repeat;
172 | }
173 | .olControlEditingToolbar {
174 | float:right;
175 | right: 0px;
176 | height: 30px;
177 | width: 200px;
178 | }
179 | .olControlEditingToolbar div {
180 | float:right;
181 | width: 24px;
182 | height: 24px;
183 | margin: 5px;
184 | }
185 | .olControlEditingToolbar .olControlNavigationItemActive {
186 | background-image: url("img/editing_tool_bar.png");
187 | background-repeat: no-repeat;
188 | background-position: -103px -23px;
189 | }
190 | .olControlEditingToolbar .olControlNavigationItemInactive {
191 | background-image: url("img/editing_tool_bar.png");
192 | background-repeat: no-repeat;
193 | background-position: -103px -0px;
194 | }
195 | .olControlEditingToolbar .olControlDrawFeaturePointItemActive {
196 | background-image: url("img/editing_tool_bar.png");
197 | background-repeat: no-repeat;
198 | background-position: -77px -23px;
199 | }
200 | .olControlEditingToolbar .olControlDrawFeaturePointItemInactive {
201 | background-image: url("img/editing_tool_bar.png");
202 | background-repeat: no-repeat;
203 | background-position: -77px -0px;
204 | }
205 | .olControlEditingToolbar .olControlDrawFeaturePathItemInactive {
206 | background-image: url("img/editing_tool_bar.png");
207 | background-repeat: no-repeat;
208 | background-position: -51px 0px;
209 | }
210 | .olControlEditingToolbar .olControlDrawFeaturePathItemActive {
211 | background-image: url("img/editing_tool_bar.png");
212 | background-repeat: no-repeat;
213 | background-position: -51px -23px;
214 | }
215 | .olControlEditingToolbar .olControlDrawFeaturePolygonItemInactive {
216 | background-image: url("img/editing_tool_bar.png");
217 | background-repeat: no-repeat;
218 | background-position: -26px 0px;
219 | }
220 | .olControlEditingToolbar .olControlDrawFeaturePolygonItemActive {
221 | background-image: url("img/editing_tool_bar.png");
222 | background-repeat: no-repeat;
223 | background-position: -26px -23px ;
224 | }
225 |
226 | .olHandlerBoxZoomBox {
227 | border: 2px solid red;
228 | position: absolute;
229 | background-color: white;
230 | opacity: 0.50;
231 | font-size: 1px;
232 | filter: alpha(opacity=50);
233 | }
234 |
235 | /*
236 | * Due to current limitations in the OpenLayers code, you can only
237 | * replace this image with another image which is 17px x 17px.
238 | */
239 | .olPopupCloseBox {
240 | background: url("chrome://zotero-maps/skin/img/close.gif") no-repeat;
241 | cursor: pointer;
242 | }
243 |
244 | .olControlNoSelect {
245 | -moz-user-select: none;
246 | }
247 |
--------------------------------------------------------------------------------
/chrome/content/zotero-maps/ui.js:
--------------------------------------------------------------------------------
1 | var map, markers, loadingPanel;
2 | var items_to_load = 0;
3 | var llproj = new OpenLayers.Projection("EPSG:4326");
4 | var googproj = new OpenLayers.Projection("EPSG:900913");
5 |
6 | var Feature = OpenLayers.Class(OpenLayers.Feature, {
7 | popupClass: OpenLayers.Popup.FramedCloud,
8 | mouseDown: function (evt) {
9 | var existing = map.popups[0];
10 | if (existing && existing.feature == this) {
11 | existing.toggle();
12 | } else {
13 |
14 | var popup = this.createPopup(true);
15 | var html = ''+this.data.name+' ('+this.data.items.length+')';
16 | for (var i = 0; i'
20 | +' '
21 | +''
22 | + item.title + ''
23 | + '';
24 | }
25 | popup.feature = this;
26 | popup.setContentHTML(html);
27 | map.addPopup(popup,true);
28 | popup.show();
29 | }
30 | OpenLayers.Event.stop(evt);
31 | return true;
32 | }
33 | });
34 |
35 | function get_items_from_zotero () {
36 | var path = window.location.pathname.split("/");
37 | var type = path[1];
38 | var ids = path[2];
39 | var results;
40 | switch (type) {
41 | case 'collection':
42 | var col = Zotero.Collections.get(ids);
43 | results = col.getChildItems();
44 | break;
45 | case 'search':
46 | var s = new Zotero.Search(ids);
47 | ids = s.search();
48 | break;
49 | case 'selection':
50 | ids = Zotero.Maps.selection();
51 | break;
52 | default:
53 | type = 'library';
54 | var s = new Zotero.Search();
55 | s.addCondition('noChildren', 'true');
56 | ids = s.search();
57 | }
58 | if (!results) {
59 | var results = Zotero.Items.get(ids);
60 | }
61 | var items = [];
62 | // Only include parent items
63 | for (var i = 0; i < results.length; i++) {
64 | if (!results[i].getSource()) {
65 | items.push(results[i]);
66 | }
67 | }
68 |
69 | return items;
70 | }
71 |
72 | function add_item_to_features (item, geoname, features) {
73 | var loc = new OpenLayers.LonLat(geoname.lng, geoname.lat);
74 | var key = loc.toShortString();
75 | loc.transform(llproj,googproj);
76 |
77 | /* Key Zotero items by lat/lon so that citations can be
78 | * grouped by location. */
79 |
80 | if (!features[key]) {
81 | var size = new OpenLayers.Size(16,28);
82 | var offset = new OpenLayers.Pixel(-(size.w/2), -size.h);
83 | var zoteroIcon = new OpenLayers.Icon('chrome://zotero-maps/skin/img/zotero_16x28.png',size,offset);
84 | var feature = new Feature(markers, loc, {name:geoname.name, items:[], icon:zoteroIcon.clone()});
85 | var marker = feature.createMarker();
86 | markers.addMarker(marker);
87 | marker.events.register("mousedown", feature, feature.mouseDown);
88 | features[key] = feature;
89 | }
90 | var citation = {
91 | title: item.getDisplayTitle(),
92 | id: item.getID(),
93 | type: item.getType(),
94 | typeIcon: item.getImageSrc()
95 | };
96 | features[key].data.items.push(citation);
97 | };
98 |
99 | function lookup_item_for_features(item, placename, features) {
100 | /* If we have the placename cached, use the cached result;
101 | * Otherwise, call the geonames search service. */
102 | var geoname = Zotero.Maps.get(placename);
103 | if (geoname) {
104 | /* Is the location known? */
105 | if (geoname.lat != 0.0 && geoname.lng != 0.0) {
106 | add_item_to_features(item, geoname, features);
107 | }
108 | } else {
109 | if (!items_to_load) {
110 | loadingPanel.maximizeControl();
111 | }
112 | items_to_load++;
113 | Zotero.Maps.query(item, placename, function (item, geoname) {
114 | items_to_load--;
115 | if (!items_to_load) {
116 | loadingPanel.minimizeControl();
117 | }
118 | if (geoname != "UNKNOWN"){
119 | add_item_to_features(item, geoname, features);
120 | }
121 | });
122 | }
123 | }
124 |
125 | function populate_map (field, max_items) {
126 |
127 | var items = get_items_from_zotero();
128 | var features = {};
129 | if (items.length > max_items) {
130 | alert("Found " + items.length + " matching items; "
131 | + "only displaying the first " + max_items + ".");
132 | } else {
133 | max_items = items.length;
134 | }
135 | for(var j=0; j= limit) {
164 | return OpenLayers.Util.getImagesLocation() + "blank.gif";
165 | } else {
166 | x = ((x % limit) + limit) % limit;
167 | return this.url + z + "/" + x + "/" + y + "." + this.type;
168 | }
169 | }
170 |
171 | function onLoad() {
172 | // avoid pink tiles
173 | OpenLayers.IMAGE_RELOAD_ATTEMPTS = 3;
174 | OpenLayers.Util.onImageLoadErrorColor = "white";
175 |
176 | loadingPanel = new OpenLayers.Control.LoadingPanel();
177 | var options = {
178 | projection: googproj,
179 | displayProjection: llproj,
180 | units: "m",
181 | maxResolution: 156543,
182 | maxExtent: new OpenLayers.Bounds(-20037508, -20037508, 20037508, 20037508),
183 | numZoomLevels: 12,
184 | controls: [
185 | new OpenLayers.Control.PanZoomBar(),
186 | new OpenLayers.Control.KeyboardDefaults(),
187 | new OpenLayers.Control.Navigation(),
188 | new OpenLayers.Control.Attribution(),
189 | loadingPanel
190 | ],
191 | //style:null
192 | };
193 |
194 | map = new OpenLayers.Map('map', options);
195 |
196 | /*** Google Maps won't work without a license key...
197 | * and Google doesn't give them out for zotero:// or chrome:// APIs...
198 | var gmap = new OpenLayers.Layer.Google( "Map" , {
199 | type: G_NORMAL_MAP, sphericalMercator:true,'maxZoomLevel':12});
200 | */
201 |
202 | // create OSM layer
203 | var mapnik = new OpenLayers.Layer.TMS(
204 | "OpenStreetMap (Mapnik)",
205 | "http://tile.openstreetmap.org/",
206 | {
207 | type: 'png', getURL: osm_getTileURL,
208 | displayOutsideMaxExtent: true,
209 | attribution: 'Map provided by ' +
210 | 'OpenStreetMap',
211 | transitionEffect: 'resize',
212 | //wrapDateLine: true
213 | }
214 | );
215 |
216 | markers = new OpenLayers.Layer.Markers("markers");
217 | map.addLayers([mapnik, markers]);
218 |
219 | loadingPanel.setVisible(false);
220 |
221 | //turn off default layer event stuff for the loadingPanel...hack.
222 | for (var i = 0; i < map.layers.length; i++) {
223 | var layer = map.layers[i];
224 | layer.events.unregister('loadstart', loadingPanel);
225 | layer.events.unregister('loadend', loadingPanel);
226 | }
227 | populate_map("place", 100);
228 | }
229 |
230 |
--------------------------------------------------------------------------------
/README.rst:
--------------------------------------------------------------------------------
1 | ===========
2 | Zotero Maps
3 | ===========
4 |
5 | ------------
6 | Introduction
7 | ------------
8 |
9 | Welcome to Zotero Maps, a plugin for displaying your Zotero_ collections
10 | on a map in your Firefox web browser.
11 |
12 | Zotero Maps uses OpenLayers_ to display geographic markers associated
13 | with your Zotero items on a map in Firefox. The locations of these markers
14 | are generated by taking the value of the *place* field for each item,
15 | and sending it to the GeoNames_ API to get a latitude/longitude
16 | coordinate. Items associated with the same place name are grouped
17 | together on the map. The background map used in Zotero Maps comes
18 | from the OpenStreetMap_ project.
19 |
20 | Zotero Maps is Free and Open Source software, and is distributed under
21 | the `same terms`_ as Zotero itself.
22 |
23 | ------------
24 | Users' Guide
25 | ------------
26 |
27 | This Users' Guide for Zotero Maps presumes that you already have Zotero
28 | installed and are familiar with how it works. Additionally, we assume
29 | that you have already added a few items to your Zotero library.
30 |
31 | Installation
32 | ~~~~~~~~~~~~
33 |
34 | Installing Zotero Maps is very simple: Open the `Zotero Maps extension`_ in
35 | your Firefox browser. You will be prompted to confirm the installation and
36 | restart your browser. Please note that you must have a working Internet
37 | connection when using Zotero Maps for it to work properly.
38 |
39 | General Use
40 | ~~~~~~~~~~~
41 |
42 | Once you've restarted Firefox, open Zotero, and browse around your library
43 | until you find an item or a collection containing items that have the *place*
44 | field set. Select this item or collection by clicking on it, then go to the
45 | *Actions* menu (the dropdown menu in the Zotero window with the 'gear' icon),
46 | and click on *View as Map*.
47 |
48 | A map will open in your browser window. Since this is presumably the first time
49 | you have used Zotero Maps, a spinner will be displayed while the locations of
50 | your items are being looked up in the GeoNames_ database. This may take a
51 | while, particularly if your collection is large, so please be patient.
52 |
53 | Once all the items have loaded, the spinner will disappear, and you can
54 | browse the items on the map. You can pan by dragging the map with your mouse,
55 | and zoom in and out using the controls on the left hand side of the map. You
56 | can also zoom in by double-clicking on the map.
57 |
58 | Click on one of the place markers. A balloon will pop up, showing the name of
59 | the place, and how many items are associated with it. Additionally, the items
60 | associated with that place will be listed underneath it. The title of each item
61 | is hyperlinked, so you can click on it to open that item in Zotero, and view
62 | its details. Clicking the 'X' button in the balloon, or clicking on the marker
63 | will cause the balloon to close.
64 |
65 | Congratulations, you're using Zotero Maps! From now on, you can select your
66 | whole library, a single collection, or one or even multiple items, and go to
67 | *Actions -> View as Map* to see them displayed in Zotero Maps.
68 |
69 | Considerations
70 | ~~~~~~~~~~~~~~
71 |
72 | The locations fetched from GeoNames are cached in your Zotero data directory,
73 | so that the next time you open the map, they should display instantly. Also,
74 | after you install Zotero Maps, items will be looked up *as they are added* to
75 | your library, which should also reduce the map loading time in the future.
76 |
77 | **Please note** that it is quite likely that *not all* of the items you have
78 | selected will appear on the map. If this is the case, it is likely that the
79 | *place* field is not set on the item in question, or that GeoNames couldn't
80 | figure out where in the world it referred to. Try editing the *place* field on
81 | that item and try loading the map again.
82 |
83 | One other note of warning: Due to the limitations of Firefox, only about 100
84 | different items can be displayed in Zotero Maps at once. If you have a large
85 | collection and would like to see this corrected in a future version of Zotero
86 | Maps, please `contact us`_.
87 |
88 | Acknowledgements
89 | ~~~~~~~~~~~~~~~~
90 |
91 | Zotero Maps was built by Entropy Free LLC, with the support of the Center for
92 | History and New Media. Schuyler Erle and Tim "Chippy" Waters wrote most of
93 | the code, with assistance from Swapnil Hajare, Kanhaiya Kale, and others.
94 |
95 | If you like Zotero Maps and find it useful, please consider supporting any of
96 | the great projects on which Zotero Maps is built, e.g. Zotero_, OpenLayers_,
97 | OpenStreetMap_, or GeoNames_. All four of these terrific projects provide Free
98 | and Open software or services to the community, and deserve your support.
99 |
100 | If you would like to see Zotero Maps improved, please feel free to `contact
101 | us`_ with your ideas, comments, suggestions, bug reports, et cetera. If you
102 | would like to provide financial support for the development of specific
103 | features, please `contact us`_. If you would like to contribute code to Zotero
104 | Maps, please read on...
105 |
106 | -----------------
107 | Developers' Guide
108 | -----------------
109 |
110 | We specifically invite Free and Open Source developers to contribute
111 | patches to Zotero Maps. What follows is a walk through the code for anyone
112 | interested in improving or extending Zotero Maps.
113 |
114 | We presume that you have a working knowledge of JavaScript, Zotero,
115 | and Firefox extension development generally. Before continuing, please
116 | be sure that you have read and are familiar with:
117 |
118 | * Zotero's `Getting Started`_ guide for developers
119 | * Zotero's `development documentation`_ generally
120 | * The Zotero Maps `Users' Guide`_
121 |
122 | Overview
123 | ~~~~~~~~
124 |
125 | The Zotero Maps code base is structured like a normal Zotero plugin in most
126 | respects. The ``install.rdf`` and ``chrome.manifest`` files in the top-level
127 | are exactly what you'd expect.
128 |
129 | The core plugin code all lives in ``chrome/content/zotero-maps/``, while the
130 | static content is in ``chrome/skin/default/zotero-maps/``. One exception is the
131 | OpenLayers library, which does not require Firefox extension privileges at
132 | runtime. Consequently, the library lives in the ``skin/`` directory, along with
133 | its static content.
134 |
135 | A ``Makefile`` is included with the distribution. We use this to build the
136 | ``.xpi`` file. We don't package up the chrome directory as a ``.jar`` file;
137 | please let us know if you can suggest a particular reason why we should.
138 |
139 | overlay.xul
140 | ~~~~~~~~~~~
141 |
142 | The ``overlay.xul`` file defines the UI overlay for the Zotero Maps plugin. It
143 | sets up a single menu option in the tools dropdown, and calls ``include.js`` to
144 | bootstrap the plugin at load time.
145 |
146 | include.js
147 | ~~~~~~~~~~
148 |
149 | The ``include.js`` file creates a core ``Zotero`` object using the XPCOM API.
150 | Additionally, it loads the OpenLayers library, and loads ``setup.js`` to
151 | initialize the Zotero Maps plugin.
152 |
153 | setup.js
154 | ~~~~~~~~
155 |
156 | The ``setup.js`` file contains the definition of the ``Zotero.Maps`` singleton
157 | object that provides the core functionality of the Zotero Maps plugin.
158 |
159 |
160 | The ``init`` method of the ``Zotero.Maps`` object configures the plugin:
161 |
162 | * With the Mozilla XPCOM API, it maps the ``zotero://maps`` URI to
163 | ``ui.html``, which provides the main visualization interface of Zotero
164 | Maps.
165 | * Using the Zotero API, ``init`` creates a new local SQLite database,
166 | with a single table, ``cache``, used to store geocoding results from
167 | geonames.org.
168 | * Also, using the Zotero API, ``init`` registers the ``notifierCallback``
169 | method to be called when new items are added to the Zotero collection,
170 | so that they can be geocoded according to their ``place`` attribute
171 | immediately, if possible.
172 |
173 | The ``get`` and ``set`` methods wrap access to the ``cache`` table. The
174 | name, latitude, and longitude from all geonames results are stored there.
175 |
176 | The ``query`` method is used to perform lookups against the Geonames API,
177 | using the OpenLayers XML HTTP request API. The request is asynchronous, so
178 | the results are passed to the ``query_callback`` function. If the API
179 | lookup yields results, the results are stored in the cache, and, if a UI
180 | callback was provided, it's then called with the result.
181 |
182 | Typically, the Geonames API returns multiple matches for a given place
183 | name, order more or less by importance. The first query result is used by
184 | Zotero Maps by default, as it's usually the one intended. If Geonames
185 | couldn't identify the place name, the place is cached with the coordinates
186 | (0,0), to mark it as unknown. A future version of Zotero Maps should
187 | provide a user interface to allow the user to choose from the full list of
188 | Geonames query results, or to allow manual correction, if the first result
189 | returned should turn out not to be the one intended.
190 |
191 | Finally, the ``load`` method of the ``Zotero.Maps`` object provides the
192 | hooks for the integration with the Zotero UI in ``overlay.xul``. The logic
193 | for deciding what to display on the map is as follows:
194 |
195 | * If one or more particular items are selected in the Zotero UI, map them.
196 | * If a collection is selected, map the items in it.
197 | * If a saved search is selected, map the items in it.
198 | * Otherwise, attempt to map the entire library.
199 |
200 | ui.html
201 | ~~~~~~~
202 |
203 | The ``ui.html`` file provides the main visualization interface for the
204 | plugin, which is a very basic OpenLayers application. It loads OpenLayers
205 | and plugin-specific CSS from the ``skin`` directory, and then loads the
206 | Zotero Maps API, the OpenLayers API, and the Zotero Maps UI code, in that
207 | order. Since this file loads ``chrome://`` URLs, it is kept in the
208 | ``chrome/content/`` directory, in order to have permissions to do so.
209 |
210 | ui.js
211 | ~~~~~
212 |
213 | The main Zotero Maps UI code lives in ``ui.js`` and provides the map
214 | display and interaction.
215 |
216 | The ``onLoad`` function is called by the browser when ``ui.html`` loads.
217 | This function creates an ``OpenLayers.Map`` object, and configures it with
218 | various UI controls, including pan/zoom, keyboard and mouse navigation,
219 | base map attribution, and the loading spinner. The map is configured to
220 | display in the global spherical Mercator projection, to match the
221 | OpenStreetMap tiles used as the basemap. (Note that this is the same
222 | projection used by Google Maps.)
223 |
224 | The ``populate_map`` function is called by ``onLoad`` to fetch the selected
225 | items from Zotero, and display them on the map via
226 | ``lookup_item_for_features``. This function looks up the items in the cache
227 | via the Zotero Maps API described above, and, when item places are not
228 | present, uses the API to query the place names on Geonames and cache them.
229 |
230 | As each item is loaded, ``add_item_to_features`` is called. This function
231 | creates a marker on the map for each coordinate pair, if one isn't already
232 | present, and adds the item to the popup listing for that marker. If a
233 | marker is already present at that location, the item is simply added to the
234 | list. Each marker is created with a UI callback that opens a popup balloon
235 | showing the list of items matching the marker's location. Each list item is
236 | hotlinked to its entry in the Zotero UI, so that clicking on an item listed
237 | in a popup balloon opens its details in the Zotero panel.
238 |
239 | OpenLayers
240 | ~~~~~~~~~~
241 |
242 | The current version of Zotero Maps ships with a custom build of OpenLayers
243 | 2.7. The build configuration is kept in a file called ``zotero.cfg`` in the
244 | Zotero Maps source tree. The loading panel code comes from the OpenLayers
245 | ``contrib`` SVN tree, and was added manually to the OL source tree prior to
246 | building the compressed version of the library.
247 |
248 | Patches
249 | ~~~~~~~
250 |
251 | We wholeheartedly welcome developers to `contact us`_ by email to submit
252 | patches to Zotero Maps. Feature and bug fix submissions should by and large
253 | follow the same JavaScript coding conventions used in the current code base.
254 | Thank you in advance for your interest!
255 |
256 |
257 | .. _Zotero: http://www.zotero.org/
258 | .. _OpenLayers: http://www.openlayers.org/
259 | .. _GeoNames: http://www.geonames.org/
260 | .. _OpenStreetMap: http://www.openstreetmap.org/
261 | .. _`same terms`: http://www.opensource.org/licenses/ecl1.php
262 | .. _`Zotero Maps extension`: http://zotero.entropyfree.com/download/zotero-maps.xpi
263 | .. _`Getting Started`: http://www.zotero.org/support/dev/getting_started
264 | .. _`development documentation`: http://www.zotero.org/support/dev/start
265 | .. _`contact us`: mailto:zotero@entropyfree.com
266 |
--------------------------------------------------------------------------------