Parser¶
The first thing we need is a parser that can take the file and turn it into usable ol.Feature
instances.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 | goog.declareModuleId('plugin.georss.GeoRSSParser');
import {PROJECTION} from 'opensphere/src/os/map/map.js';
const Feature = goog.require('ol.Feature');
const LineString = goog.require('ol.geom.LineString');
const Point = goog.require('ol.geom.Point');
const Polygon = goog.require('ol.geom.Polygon');
const {isDocument, parse} = goog.require('ol.xml');
const {default: IParser} = goog.requireType('os.parse.IParser');
/**
* Parser for GeoRSS feeds
* @implements {IParser<Feature>}
* @template T
* @constructor
*/
export default class GeoRSSParser {
/**
* Constructor.
*/
constructor() {
/**
* @type {?Document}
* @protected
*/
this.document = null;
/**
* @type {?NodeList}
* @protected
*/
this.entries = null;
/**
* @type {number}
* @protected
*/
this.nextIndex = 0;
}
/**
* @inheritDoc
*/
setSource(source) {
if (isDocument(source)) {
this.document = /** @type {Document} */ (source);
} else if (typeof source === 'string') {
this.document = parse(source);
}
if (this.document) {
this.entries = this.document.querySelectorAll('entry');
}
}
/**
* @inheritDoc
*/
cleanup() {
this.document = null;
this.entries = null;
this.nextIndex = 0;
}
/**
* @inheritDoc
*/
hasNext() {
return this.entries != null && this.entries.length > this.nextIndex;
}
/**
* @inheritDoc
*/
parseNext() {
var nextEntry = this.entries[this.nextIndex++];
var children = nextEntry.childNodes;
var properties = {};
for (var i = 0, n = children.length; i < n; i++) {
var el = /** @type {Element} */ (children[i]);
if (el.localName === 'link') {
properties[el.localName] = el.getAttribute('href');
} else if (el.namespaceURI === 'http://www.georss.org/georss') {
var geom = parseGeometry(el);
if (geom) {
properties['geometry'] = geom;
}
} else {
properties[el.localName] = el.textContent;
}
}
return new Feature(properties);
}
}
/**
* @param {Element} el The element to parse
* @return {ol.geom.Geometry|undefined} the geometry
*/
export const parseGeometry = function(el) {
switch (el.localName) {
case 'point':
return parsePoint(el);
case 'line':
return parseLine(el);
case 'polygon':
return parsePolygon(el);
default:
break;
}
};
/**
* @param {Element} el The element to parse
* @return {Point|undefined} The point geometry
*/
const parsePoint = function(el) {
var coords = parseCoords(el);
if (!coords || coords.length === 0) {
// no coords found!
return;
}
return new Point(coords[0]);
};
/**
* @param {Element} el The element to parse
* @return {LineString|undefined} The line geometry
* @private
*/
const parseLine = function(el) {
var coords = parseCoords(el);
if (!coords) {
// no coords found!
return;
}
if (coords.length < 2) {
// need at least 2 coords for line!
return;
}
return new LineString(coords);
};
/**
* @param {Element} el The element to parse
* @return {Polygon|undefined} The polygon geometry
*/
const parsePolygon = function(el) {
var coords = parseCoords(el);
if (!coords) {
// no coords found!
return;
}
if (coords.length < 3) {
// need at least 3 coords for polygon!
return;
}
return new Polygon([coords]);
};
/**
* @param {Element} el The element to parse
* @return {Array<ol.Coordinate>|undefined} The array of coordinates
*/
const parseCoords = function(el) {
var parts = el.textContent.trim().split(/\s+/);
if (parts.length % 2 !== 0) {
// odd amount of numbers, cannot produce pairs!
return;
}
var coords = [];
for (var i = 1, n = parts.length; i < n; i += 2) {
var lat = parseFloat(parts[i - 1]);
var lon = parseFloat(parts[i]);
if (isNaN(lat) || isNaN(lon)) {
// could not parse all lat/lons of coordinates!
return;
}
var coord = [lon, lat];
// convert to the application projection
coords.push(ol.proj.fromLonLat(coord, PROJECTION));
}
return coords;
};
|
Whew. That was a lot for one step. It is not exhaustive, and a full implementation would want to support RSS in addition to Atom as well as the <georss:elev>
tag. However, it still would not be complete without some tests.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 | goog.require('ol.xml');
goog.require('plugin.georss.GeoRSSParser');
describe('plugin.georss.GeoRSSParser', function() {
const {parse} = goog.module.get('ol.xml');
const {default: GeoRSSParser} = goog.module.get('plugin.georss.GeoRSSParser');
it('should parse points correctly', function() {
var el = parse('<point> 40 -105 ' +
'</point>').firstElementChild;
var geom = GeoRSSParser.parseGeometry(el);
expect(geom instanceof ol.geom.Point).toBe(true);
expect(geom.getCoordinates()[0]).toBe(-105);
expect(geom.getCoordinates()[1]).toBe(40);
});
it('should choose the first point if there is more than one', function() {
var el = parse('<point>40 -105 50 -95</point>').firstElementChild;
var geom = GeoRSSParser.parseGeometry(el);
expect(geom instanceof ol.geom.Point).toBe(true);
expect(geom.getCoordinates()[0]).toBe(-105);
expect(geom.getCoordinates()[1]).toBe(40);
});
it('should return undefined when pairs for point are incomplete', function() {
var el = parse('<point>40 -105 50</point>').firstElementChild;
var geom = GeoRSSParser.parseGeometry(el);
expect(geom).toBe(undefined);
});
it('should return undefined when points do not contain adequate coordinate pairs', function() {
var el = parse('<point></point>').firstElementChild;
var geom = GeoRSSParser.parseGeometry(el);
expect(geom).toBe(undefined);
});
it('should not parse nonsense', function() {
var el = parse('<point>10 yang</point>').firstElementChild;
var geom = GeoRSSParser.parseGeometry(el);
expect(geom).toBe(undefined);
el = parse('<point>ying 10</point>').firstElementChild;
geom = GeoRSSParser.parseGeometry(el);
expect(geom).toBe(undefined);
});
it('should parse lines correctly', function() {
var el = parse('<line>40 100 50 110</line>').firstElementChild;
var geom = GeoRSSParser.parseGeometry(el);
expect(geom instanceof ol.geom.LineString).toBe(true);
expect(geom.getCoordinates()[0][0]).toBe(100);
expect(geom.getCoordinates()[0][1]).toBe(40);
expect(geom.getCoordinates()[1][0]).toBe(110);
expect(geom.getCoordinates()[1][1]).toBe(50);
});
it('should return undefined when pairs for lines are incomplete', function() {
var el = parse('<line>40 100 50 110 60</line>').firstElementChild;
var geom = GeoRSSParser.parseGeometry(el);
expect(geom).toBe(undefined);
});
it('should return undefined when lines do not contain adequate coordinate pairs', function() {
var el = parse('<line>40 100</line>').firstElementChild;
var geom = GeoRSSParser.parseGeometry(el);
expect(geom).toBe(undefined);
});
it('should parse polygons correctly', function() {
var el = parse('<polygon>40 100 50 110 60 100</polygon>').firstElementChild;
var geom = GeoRSSParser.parseGeometry(el);
expect(geom instanceof ol.geom.Polygon).toBe(true);
expect(geom.getCoordinates()[0][0][0]).toBe(100);
expect(geom.getCoordinates()[0][0][1]).toBe(40);
expect(geom.getCoordinates()[0][1][0]).toBe(110);
expect(geom.getCoordinates()[0][1][1]).toBe(50);
expect(geom.getCoordinates()[0][2][0]).toBe(100);
expect(geom.getCoordinates()[0][2][1]).toBe(60);
});
it('should return undefined when pairs for polygons are incomplete', function() {
var el = parse('<polygon>40 100 50 110 60 100 70</polygon>').firstElementChild;
var geom = GeoRSSParser.parseGeometry(el);
expect(geom).toBe(undefined);
});
it('should return undefined when polygons do not contain adequate coordinate pairs', function() {
var el = parse('<polygon>40 100 50 110</polygon>').firstElementChild;
var geom = GeoRSSParser.parseGeometry(el);
expect(geom).toBe(undefined);
});
it('should return undefined for incorrect tag names', function() {
var el = parse('<something>is wrong here</something>').firstElementChild;
expect(GeoRSSParser.parseGeometry(el)).toBe(undefined);
});
it('should parse GeoRSS feeds', function() {
var p = new GeoRSSParser();
var feed = '<?xml version="1.0" encoding="utf-8"?>' +
'<feed xmlns="http://www.w3.org/2005/Atom" xmlns:georss="http://www.georss.org/georss">' +
'<title>Earthquakes</title>' +
'<subtitle>International earthquake observation labs</subtitle>' +
'<link href="http://example.org/"/>' +
'<updated>2005-12-13T18:30:02Z</updated>' +
'<author>' +
'<name>Dr. Thaddeus Remor</name>' +
'<email>tremor@quakelab.edu</email>' +
'</author>' +
'<id>urn:uuid:60a76c80-d399-11d9-b93C-0003939e0af6</id>' +
'<entry>' +
'<title>M 3.2, Mona Passage</title>' +
'<link href="http://example.org/2005/09/09/atom01"/>' +
'<id>urn:uuid:1225c695-cfb8-4ebb-aaaa-80da344efa6a</id>' +
'<updated>2005-08-17T07:02:32Z</updated>' +
'<summary>We just had a big one.</summary>' +
'<georss:point>45.256 -71.92</georss:point>' +
// we want to make sure this is properly ignored for now, we'll leave it as an
// exercise for the user to implement it
'<georss:elev>-19372</georss:elev>' +
'</entry>' +
'</feed>';
p.setSource(feed);
expect(p.hasNext()).toBe(true);
var source = parse(feed);
p.setSource(source);
expect(p.hasNext()).toBe(true);
var feature = p.parseNext();
expect(p.hasNext()).toBe(false);
expect(feature instanceof ol.Feature).toBe(true);
expect(feature.get('title')).toBe('M 3.2, Mona Passage');
expect(feature.get('link')).toBe('http://example.org/2005/09/09/atom01');
expect(feature.get('id')).toBe('urn:uuid:1225c695-cfb8-4ebb-aaaa-80da344efa6a');
expect(feature.get('updated')).toBe('2005-08-17T07:02:32Z');
expect(feature.get('summary')).toBe('We just had a big one.');
expect(feature.getGeometry() instanceof ol.geom.Point).toBe(true);
p.cleanup();
expect(p.hasNext()).toBe(false);
expect(p.document).toBe(null);
expect(p.entries).toBe(null);
expect(p.nextIndex).toBe(0);
});
it('should not use other potential sources', function() {
var p = new GeoRSSParser();
p.setSource({something: true});
expect(p.document).toBe(null);
expect(p.hasNext()).toBe(false);
});
});
|
There. Now we can fully test our parser with yarn test
.