djatoka 0.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,256 @@
1
+ /* Copyright (c) UNC Chapel Hill University Library, created by Hugh A. Cayless
2
+ * and revised by J. Clifford Dyer. Published under the Clear BSD licence.
3
+ * See http://svn.openlayers.org/trunk/openlayers/license.txt for the full
4
+ * text of the license.
5
+ */
6
+
7
+
8
+ /**
9
+ * @requires OpenLayers/Layer/Grid.js
10
+ * @requires OpenLayers/Tile/Image.js
11
+ */
12
+
13
+ /**
14
+ * Class: OpenLayers.Layer.OpenURL
15
+ *
16
+ * Inherits from:
17
+ * - <OpenLayers.Layer.Grid>
18
+ */
19
+ OpenLayers.Layer.OpenURL = OpenLayers.Class(OpenLayers.Layer.Grid, {
20
+
21
+ /**
22
+ * APIProperty: isBaseLayer
23
+ * {Boolean}
24
+ */
25
+ isBaseLayer: true,
26
+
27
+ /**
28
+ * APIProperty: tileOrigin
29
+ * {<OpenLayers.Pixel>}
30
+ */
31
+ tileOrigin: null,
32
+
33
+ url_ver: 'Z39.88-2004',
34
+ rft_id: null,
35
+ svc_id: "info:lanl-repo/svc/getRegion",
36
+ svc_val_fmt: "info:ofi/fmt:kev:mtx:jpeg2000",
37
+ format: null,
38
+ tileHeight: null,
39
+
40
+ /**
41
+ * Constructor: OpenLayers.Layer.OpenURL
42
+ *
43
+ * Parameters:
44
+ * name - {String}
45
+ * url - {String}
46
+ * options - {Object} Hashtable of extra options to tag onto the layer
47
+ */
48
+ initialize: function(name, url, options) {
49
+ var newArguments = [];
50
+ newArguments.push(name, url, {}, options);
51
+ OpenLayers.Layer.Grid.prototype.initialize.apply(this, newArguments);
52
+ this.rft_id = options.rft_id;
53
+ this.format = options.format;
54
+ // Get image metadata if it hasn't been set
55
+ if (!options.imgMetadata) {
56
+ var request = OpenLayers.Request.issue({url: options.metadataUrl, async: false});
57
+ this.imgMetadata = eval('(' + request.responseText + ')');
58
+ } else {
59
+ this.imgMetadata = options.imgMetadata;
60
+ }
61
+
62
+ var minLevel = this.getMinLevel();
63
+
64
+ // viewerLevel is the smallest useful zoom level: i.e., it is the largest level that fits entirely
65
+ // within the bounds of the viewer div.
66
+ var viewerLevel = Math.ceil(Math.min(minLevel, Math.max(
67
+ (Math.log(this.imgMetadata.width) - Math.log(OpenLayers.Layer.OpenURL.viewerWidth)),
68
+ (Math.log(this.imgMetadata.height) - Math.log(OpenLayers.Layer.OpenURL.viewerHeight)))/
69
+ Math.log(2)));
70
+ this.zoomOffset = minLevel - viewerLevel;
71
+
72
+ // width at level viewerLevel
73
+ var w = this.imgMetadata.width / Math.pow(2, viewerLevel);
74
+
75
+ // height at level viewerLevel
76
+ var h = this.imgMetadata.height / Math.pow(2, viewerLevel);
77
+
78
+ this.resolutions = new Array();
79
+ for (i = viewerLevel; i >= 0; i--) {
80
+ this.resolutions.push(Math.pow(2, i));
81
+ }
82
+
83
+ this.tileSize = new OpenLayers.Size(Math.ceil(w), Math.ceil(h));
84
+ },
85
+
86
+ /**
87
+ * APIMethod:destroy
88
+ */
89
+ destroy: function() {
90
+ // for now, nothing special to do here.
91
+ OpenLayers.Layer.Grid.prototype.destroy.apply(this, arguments);
92
+ },
93
+
94
+
95
+ /**
96
+ * APIMethod: clone
97
+ *
98
+ * Parameters:
99
+ * obj - {Object}
100
+ *
101
+ * Returns:
102
+ * {<OpenLayers.Layer.OpenURL>} An exact clone of this <OpenLayers.Layer.OpenURL>
103
+ */
104
+ clone: function (obj) {
105
+
106
+ if (obj == null) {
107
+ obj = new OpenLayers.Layer.OpenURL(this.name,
108
+ this.url,
109
+ this.options);
110
+ }
111
+
112
+ //get all additions from superclasses
113
+ obj = OpenLayers.Layer.Grid.prototype.clone.apply(this, [obj]);
114
+
115
+ // copy/set any non-init, non-simple values here
116
+
117
+ return obj;
118
+ },
119
+
120
+ /**
121
+ * Method: getURL
122
+ *
123
+ * Parameters:
124
+ * bounds - {<OpenLayers.Bounds>}
125
+ *
126
+ * Returns:
127
+ * {String} A string with the layer's url and parameters and also the
128
+ * passed-in bounds and appropriate tile size specified as
129
+ * parameters
130
+ */
131
+ getURL: function (bounds) {
132
+ bounds = this.adjustBounds(bounds);
133
+ this.calculatePositionAndSize(bounds);
134
+ var z = this.map.getZoom() + this.zoomOffset;
135
+ var path = OpenLayers.Layer.OpenURL.djatokaURL + "?url_ver=" + this.url_ver + "&rft_id=" + this.rft_id +
136
+ "&svc_id=" + this.svc_id + "&svc_val_fmt=" + this.svc_val_fmt + "&svc.format=" +
137
+ this.format + "&svc.level=" + z + "&svc.rotate=0&svc.region=" + this.tilePos.lat + "," +
138
+ this.tilePos.lon + "," + this.imageSize.h + "," + this.imageSize.w;
139
+
140
+ var url = this.url;
141
+ if (url instanceof Array) {
142
+ url = this.selectUrl(path, url);
143
+ }
144
+ return url + path;
145
+ },
146
+
147
+ /**
148
+ * Method: addTile
149
+ * addTile creates a tile, initializes it, and adds it to the layer div.
150
+ *
151
+ * Parameters:
152
+ * bounds - {<OpenLayers.Bounds>}
153
+ * position - {<OpenLayers.Pixel>}
154
+ *
155
+ * Returns:
156
+ * {<OpenLayers.Tile.Image>} The added OpenLayers.Tile.Image
157
+ */
158
+ addTile:function(bounds,position) {
159
+ this.calculatePositionAndSize(bounds);
160
+ var size = this.size;
161
+ return new OpenLayers.Tile.Image(this, position, bounds,
162
+ null, this.imageSize);
163
+ },
164
+
165
+ /**
166
+ * APIMethod: setMap
167
+ * When the layer is added to a map, then we can fetch our origin
168
+ * (if we don't have one.)
169
+ *
170
+ * Parameters:
171
+ * map - {<OpenLayers.Map>}
172
+ */
173
+ setMap: function(map) {
174
+ OpenLayers.Layer.Grid.prototype.setMap.apply(this, arguments);
175
+ if (!this.tileOrigin) {
176
+ this.tileOrigin = new OpenLayers.LonLat(this.map.maxExtent.left,
177
+ this.map.maxExtent.bottom);
178
+ }
179
+ },
180
+
181
+ calculatePositionAndSize: function(bounds) {
182
+ // Have to recalculate x and y (instead of using bounds and resolution), because resolution will be off.
183
+ // Get number of tiles in image
184
+ var max = this.map.getMaxExtent();
185
+ var xtiles = Math.round( 1 / (this.tileSize.w / max.getWidth()));
186
+ // Find out which tile we're on
187
+ var xpos = Math.round((bounds.left / max.getWidth()) * xtiles);
188
+ // Set x
189
+ var x = xpos * (this.tileSize.w + 1);
190
+ var w,h;
191
+ var xExtent = max.getWidth() / this.map.getResolution();
192
+ if (xpos == xtiles - 1) {
193
+ w = xExtent % (this.tileSize.w + 1);
194
+ } else {
195
+ w = this.tileSize.w;
196
+ }
197
+ // Do the same for y
198
+ var ytiles = Math.round( 1 / (this.tileSize.h / max.getHeight()));
199
+ // Djatoka's coordinate system is top-down, not bottom-up, so invert for y
200
+ var y = max.getHeight() - bounds.top;
201
+ y = y < 0? 0 : y;
202
+ var ypos = Math.round((y / max.getHeight()) * ytiles);
203
+ var y = ypos * (this.tileSize.h + 1);
204
+ var yExtent = max.getHeight() / this.map.getResolution();
205
+ if (ypos == ytiles - 1) {
206
+ h = yExtent % (this.tileSize.h + 1);
207
+ } else {
208
+ h = this.tileSize.h;
209
+ }
210
+ this.tilePos = new OpenLayers.LonLat(x,y);
211
+ this.imageSize = new OpenLayers.Size(w,h);
212
+ },
213
+
214
+ getImageMetadata: function() {
215
+ return this.imgMetadata;
216
+ },
217
+
218
+ getResolutions: function() {
219
+ return this.resolutions;
220
+ },
221
+
222
+ getTileSize: function() {
223
+ return this.tileSize;
224
+ },
225
+
226
+ getMinLevel: function() {
227
+ // Versions of djatoka from before 4/17/09 have levels set to the
228
+ // number of levels encoded in the image. After this date, that
229
+ // number is assigned to the new dwtLevels, and levels contains the
230
+ // number of levels between the full image size and the minimum
231
+ // size djatoka could return. We want the lesser of these two numbers.
232
+
233
+ var levelsInImg;
234
+ var levelsToDjatokaMin;
235
+ if (this.imgMetadata.dwtLevels === undefined) {
236
+ var maxImgDimension = Math.max(this.imgMetadata.width,
237
+ this.imgMetadata.height);
238
+ levelsInImg = this.imgMetadata.levels;
239
+ levelsToDjatokaMin = Math.floor((Math.log(maxImgDimension) -
240
+ Math.log(OpenLayers.Layer.OpenURL.minDjatokaLevelDimension)) /
241
+ Math.log(2));
242
+ } else {
243
+ var levelsInImg = this.imgMetadata.dwtLevels;
244
+ var levelsToDjatokaMin = this.imgMetadata.levels;
245
+ }
246
+ return Math.min(levelsInImg, levelsToDjatokaMin);
247
+ },
248
+
249
+ CLASS_NAME: "OpenLayers.Layer.OpenURL"
250
+ });
251
+
252
+ OpenLayers.Layer.OpenURL.viewerWidth = 512;
253
+ OpenLayers.Layer.OpenURL.viewerHeight = 512;
254
+ OpenLayers.Layer.OpenURL.minDjatokaLevelDimension = 48;
255
+ OpenLayers.Layer.OpenURL.djatokaURL = '/adore-djatoka/resolver';
256
+
@@ -0,0 +1,4 @@
1
+ if Djatoka.respond_to? :resolver
2
+ Djatoka.resolver="http://localhost/adore-djatoka/resolver"
3
+ end
4
+
@@ -0,0 +1,48 @@
1
+ //////////////////////
2
+ //$.extend({
3
+ // getUrlVars: function(){
4
+ // var vars = [], hash;
5
+ // var hashes = window.location.href.slice(window.location.href.indexOf('?') + 1).split('&');
6
+ // for(var i = 0; i < hashes.length; i++)
7
+ // {
8
+ // hash = hashes[i].split('=');
9
+ // vars.push(hash[0]);
10
+ // vars[hash[0]] = hash[1];
11
+ // }
12
+ // return vars;
13
+ // },
14
+ // getUrlVar: function(name){
15
+ // return $.getUrlVars()[name];
16
+ // }
17
+ //});
18
+ //////////////////////////////
19
+
20
+ function openlayersInit(base_url,metadataUrl, rft_id, div_identifier){
21
+
22
+ //alert(params);
23
+ //var metadataUrl = "http://localhost/adore-djatoka/resolver?url_ver=Z39.88-2004&rft_id=http://memory.loc.gov/gmd/gmd433/g4330/g4330/np000066.jp2&svc_id=info:lanl-repo/svc/getMetadata";
24
+
25
+ var OUlayer = new OpenLayers.Layer.OpenURL( "OpenURL",
26
+ base_url, {layername: 'basic',
27
+ format:'image/jpeg',
28
+ rft_id: rft_id,
29
+ metadataUrl: metadataUrl} );
30
+ var metadata = OUlayer.getImageMetadata();
31
+ var resolutions = OUlayer.getResolutions();
32
+ var maxExtent = new OpenLayers.Bounds(0, 0, metadata.width,
33
+ metadata.height);
34
+ var tileSize = OUlayer.getTileSize();
35
+ var options = {resolutions: resolutions, maxExtent: maxExtent,
36
+ tileSize: tileSize};
37
+ var map = new OpenLayers.Map( div_identifier, options);
38
+ map.addLayer(OUlayer);
39
+ var lon = metadata.width / 2;
40
+ var lat = metadata.height / 2;
41
+ map.setCenter(new OpenLayers.LonLat(lon, lat), 0);
42
+ }
43
+ ///////////////////////////////////////
44
+ // $(document).ready(function() {
45
+ // openlayersInit('http://localhost',"http://localhost/adore-djatoka/resolver?url_ver=Z39.88-2004&rft_id=http://memory.loc.gov/gmd/gmd433/g4330/g4330/np000066.jp2&svc_id=info:lanl-repo/svc/getMetadata",
46
+ // 'http://memory.loc.gov/gmd/gmd433/g4330/g4330/np000066.jp2');
47
+ // });
48
+
@@ -0,0 +1,71 @@
1
+ $:.unshift(File.dirname(__FILE__)) unless
2
+ $:.include?(File.dirname(__FILE__)) || $:.include?(File.expand_path(File.dirname(__FILE__)))
3
+
4
+ # In the context of Djatoka a <tt>uri</tt> is an Addressable::URI and a
5
+ # <tt>url</tt> is a String representation of that uri.
6
+ module Djatoka
7
+ # for use with Rails to allow for configuration
8
+ def self.resolver=(url)
9
+ if url.is_a? Djatoka::Resolver
10
+ @resolver = url
11
+ elsif url.is_a? String
12
+ @resolver = Djatoka::Resolver.new(url)
13
+ # if resolver.valid?
14
+ # @resolver = resolver
15
+ # else
16
+ # @resolver = nil
17
+ # end
18
+ end
19
+ end
20
+ def self.resolver
21
+ @resolver
22
+ end
23
+
24
+ # Allows for using curb if available. Otherwise falls back on Net::HTTP. See Djatoka::Net
25
+ def self.use_curb=(curb)
26
+ @use_curb = curb
27
+ end
28
+ def self.use_curb?
29
+ @use_curb
30
+ end
31
+ class << self
32
+ # Calls enable_actionpack
33
+ def enable
34
+ enable_actionpack
35
+ end
36
+ # Requires the Djatoka Rails view helpers
37
+ def enable_actionpack
38
+ return if ActionView::Base.instance_methods.include? :djatoka_image_tag
39
+ require 'djatoka/view_helpers'
40
+ ActionView::Base.send :include, ViewHelpers
41
+ end
42
+ end
43
+ end
44
+
45
+ begin
46
+ require 'curb'
47
+ Djatoka.use_curb = true
48
+ rescue LoadError
49
+ Djatoka.use_curb = false
50
+ end
51
+
52
+ require 'net/http'
53
+ require 'uri'
54
+
55
+ require 'djatoka/net'
56
+ require 'djatoka/resolver'
57
+ require 'djatoka/metadata'
58
+ require 'djatoka/common'
59
+ require 'djatoka/region'
60
+
61
+ require 'rubygems'
62
+ require 'addressable/uri'
63
+ require 'addressable/template'
64
+ require 'json'
65
+ require 'mash'
66
+
67
+
68
+ if defined? Rails
69
+ Djatoka.enable_actionpack if defined? ActionController
70
+ end
71
+
@@ -0,0 +1,125 @@
1
+ # In this module are some methods for some common operations to be done on images.
2
+ # These features are more experimental and more likely to break for some images
3
+ # at some resolutions.
4
+ module Djatoka::Common
5
+
6
+ # svc.region - Y,X,H,W.
7
+ # Y is the down inset value (positive) from 0 on the y axis at the max image resolution.
8
+ # X is the right inset value (positive) from 0 on the x axis at the max image resolution.
9
+ # H is the height of the image provided as response.
10
+ # W is the width of the image provided as response.
11
+
12
+ # Sets the scale to a 75x75 pixel version of the image and crops any long side
13
+ # to square it. Returns self (a Djatoka::Region.)
14
+ def smallbox
15
+ scale('75')
16
+ square_params
17
+ self
18
+ end
19
+
20
+ # An Addressable::URI for a 75x75 version of the image.
21
+ def smallbox_uri
22
+ smallbox
23
+ uri
24
+ end
25
+
26
+ # String of #smallbox_uri
27
+ def smallbox_url
28
+ smallbox_uri.to_s
29
+ end
30
+
31
+ # public alias for #square_params. Returns self (a Djatoka::Region)
32
+ def square
33
+ square_params
34
+ self
35
+ end
36
+
37
+ # Sets any parameters needed to crop the image to a square and then returns
38
+ # an Addressable::URI.
39
+ def square_uri
40
+ square_params
41
+ uri
42
+ end
43
+
44
+ # String of the #square_uri
45
+ def square_url
46
+ square_uri.to_s
47
+ end
48
+
49
+ # So far we figure the best level to ask for based on any scale parameter and
50
+ # try to compensate for any difference between the dwtLevels and djatoka levels
51
+ # so that we get a decent image returned.
52
+ def pick_best_level(metadata)
53
+ best_level = '6'
54
+ metadata_levels = metadata.all_levels
55
+ metadata_levels.keys.sort.reverse.each do |k|
56
+ if metadata_levels[k].height.to_i > query.scale.to_i and
57
+ metadata_levels[k].width.to_i > query.scale.to_i
58
+ best_level = k
59
+ end
60
+ end
61
+ # Here we try to compensate for when the dwtLevels does not match the
62
+ # djatoka levels.
63
+ if metadata.dwt_levels.to_i > metadata.levels.to_i
64
+ best_level.to_i - 2
65
+ elsif metadata.dwt_levels.to_i == metadata.levels.to_i
66
+ best_level.to_i
67
+ end
68
+ end
69
+
70
+ private
71
+
72
+ # This is an experimental way to get a decent looking square image at any level.
73
+ # The complication seems to come in where the dwtLevels are different from the
74
+ # how djatoka determines levels. See the comments for the places where the code
75
+ # tries to reconcile this difference in a way that seems to work in the cases
76
+ # seen so far.
77
+ def square_params
78
+ metadata = Djatoka::Metadata.new(resolver, rft_id).perform
79
+ if metadata
80
+ orig_height = metadata.height.to_i
81
+ orig_width = metadata.width.to_i
82
+ if query.scale and query.scale.split.length == 1
83
+ # scaling an image without picking a good level results in poor image
84
+ # quality
85
+ level(pick_best_level(metadata))
86
+ end
87
+ if query.level
88
+ # we try to compensate for when there is a difference between the
89
+ # dwtLevels and the djatoka levels. So far the only case seen is where
90
+ # the dwtLevels are greater than the djatoka levels.
91
+ if metadata.dwt_levels.to_i > metadata.levels.to_i
92
+ good_query_level = query.level.to_i + 1
93
+ # dwtLevels in the cases seen so far almost always match the djatoka levels.
94
+ elsif metadata.dwt_levels.to_i == metadata.levels.to_i
95
+ good_query_level = query.level.to_i
96
+ end
97
+ height = metadata.all_levels[good_query_level].height
98
+ width = metadata.all_levels[good_query_level].width
99
+ else
100
+ height = orig_height
101
+ width = orig_width
102
+ end
103
+ # x & y are always the inset of the original image size
104
+ # height and width are relative to the level selected and one if not already
105
+ # a square then the longest side is cropped.
106
+ if height != width
107
+ if height > width
108
+ x = '0'
109
+ y = ((orig_height - orig_width)/2).to_s
110
+
111
+ h = width.to_s
112
+ w = width.to_s
113
+ elsif width > height
114
+ y = '0'
115
+ x = ((orig_width - orig_height)/2).to_s
116
+ h = height.to_s
117
+ w = height.to_s
118
+ end
119
+ region([y,x,h,w])
120
+ end
121
+ end
122
+ end #square_params
123
+
124
+ end
125
+