diff options
-rw-r--r-- | default.cfg | 40 | ||||
-rw-r--r-- | opengts.cfg | 1 | ||||
-rw-r--r-- | src/index.html | 796 | ||||
-rw-r--r-- | src/org/traccar/Server.java | 14 | ||||
-rw-r--r-- | src/org/traccar/helper/NamedParameterStatement.java | 13 | ||||
-rw-r--r-- | src/org/traccar/http/WebServer.java | 53 | ||||
-rw-r--r-- | src/org/traccar/model/DataManager.java | 4 | ||||
-rw-r--r-- | src/org/traccar/model/DatabaseDataManager.java | 101 |
8 files changed, 966 insertions, 56 deletions
diff --git a/default.cfg b/default.cfg index e16223dfd..3c432d092 100644 --- a/default.cfg +++ b/default.cfg @@ -15,8 +15,8 @@ <!--> Create database: - CREATE TABLE devices (id INT, imei VARCHAR(16)) - CREATE TABLE positions (device_id INT, time TIMESTAMP, is_valid BOOLEAN, latitude DOUBLE, longitude DOUBLE, speed DOUBLE, course DOUBLE, power DOUBLE) + CREATE TABLE devices (id INT IDENTITY imei VARCHAR(16)) + CREATE TABLE positions (device_id INT, time TIMESTAMP, valid BOOLEAN, latitude DOUBLE, longitude DOUBLE, speed DOUBLE, course DOUBLE, power DOUBLE) <--> <!--> @@ -29,6 +29,41 @@ </entry> <!--> + imei - String + <--> + <entry key="database.insertDevice"> + INSERT INTO devices (imei) + VALUES (:imei) + </entry> + + <!--> + id - Integer + imei - String + <--> + <entry key="database.updateDevice"> + UPDATE devices + SET imei = :imei + WHERE id = :id + </entry> + + <!--> + id - Integer + <--> + <entry key="database.deleteDevice"> + DELETE FROM devices + WHERE id = :id + </entry> + + <!--> + device_id - Integer + <--> + <entry key="database.selectPosition"> + SELECT * + FROM positions + WHERE device_id = :device_id + </entry> + + <!--> device_id - Integer time - Date valid - Boolean @@ -45,6 +80,7 @@ </entry> <!-- Web interface port --> + <entry key="http.enable">true</entry> <entry key="http.port">8082</entry> <!-- Logging options --> diff --git a/opengts.cfg b/opengts.cfg index 075dd12e5..40f535723 100644 --- a/opengts.cfg +++ b/opengts.cfg @@ -41,6 +41,7 @@ </entry> <!-- Web interface port --> + <entry key="http.enable">true</entry> <entry key="http.port">8082</entry> <!-- Logging options --> diff --git a/src/index.html b/src/index.html index 1b782db82..cde73f47b 100644 --- a/src/index.html +++ b/src/index.html @@ -5,8 +5,747 @@ <link rel="stylesheet" type="text/css" href="http://cdn.sencha.io/ext-4.1.0-gpl/resources/css/ext-all.css" /> <script type="text/javascript" src="http://cdn.sencha.io/ext-4.1.0-gpl/ext-all.js"></script> -<!-- WARNING: integrate to page --> -<!-- <script type="text/javascript" src="https://raw.github.com/VinylFox/ExtJS.ux.GMapPanel/master/src/GMapPanel3.js"></script> --> +<!-- check for new version: https://raw.github.com/VinylFox/ExtJS.ux.GMapPanel/master/src/GMapPanel3.js --> + +<script type="text/javascript"> +Ext.ns('Ext.ux'); +/** + * @class Ext.ux.GMapPanel + * @extends Ext.Panel + * @author Shea Frederick + */ +Ext.define('Ext.ux.GMapPanel', { + + extend: 'Ext.panel.Panel', + + alias: 'widget.gmappanel', + + requires: ['Ext.window.MessageBox'], + /** + * @cfg {Boolean} border + * Defaults to <tt>false</tt>. See {@link Ext.Panel}.<code>{@link Ext.Panel#border border}</code>. + */ + border: false, + + /** + * @cfg {Array} respErrors + * An array of msg/code pairs. + */ + respErrors: [{ + code: 'UNKNOWN_ERROR', + msg: 'A geocoding or directions request could not be successfully processed, yet the exact reason for the failure is not known.' + },{ + code: 'ERROR', + msg: 'There was a problem contacting the Google servers.' + },{ + code: 'ZERO_RESULTS', + msg: 'The request did not encounter any errors but returns zero results.' + },{ + code: 'INVALID_REQUEST', + msg: 'This request was invalid.' + },{ + code: 'REQUEST_DENIED', + msg: 'The webpage is not allowed to use the geocoder for some reason.' + },{ + code: 'OVER_QUERY_LIMIT', + msg: 'The webpage has gone over the requests limit in too short a period of time.' + }], + /** + * @cfg {Array} locationTypes + * An array of msg/code/level pairs. + */ + locationTypes: [{ + level: 4, + code: 'ROOFTOP', + msg: 'The returned result is a precise geocode for which we have location information accurate down to street address precision.' + },{ + level: 3, + code: 'RANGE_INTERPOLATED', + msg: 'The returned result reflects an approximation (usually on a road) interpolated between two precise points (such as intersections). Interpolated results are generally returned when rooftop geocodes are unavailable for a street address.' + },{ + level: 2, + code: 'GEOMETRIC_CENTER', + msg: 'The returned result is the geometric center of a result such as a polyline (for example, a street) or polygon (region).' + },{ + level: 1, + code: 'APPROXIMATE', + msg: 'The returned result is approximate.' + }], + /** + * @cfg {String} respErrorTitle + * Defaults to <tt>'Error'</tt>. + */ + respErrorTitle : 'Error', + /** + * @cfg {String} geoErrorMsgUnable + * Defaults to <tt>'Unable to Locate the Address you provided'</tt>. + */ + geoErrorMsgUnable : 'Unable to Locate the Address you provided', + /** + * @cfg {String} geoErrorTitle + * Defaults to <tt>'Address Location Error'</tt>. + */ + geoErrorTitle : 'Address Location Error', + /** + * @cfg {String} geoErrorMsgAccuracy + * Defaults to <tt>'The address provided has a low accuracy.<br><br>{0} Accuracy.'</tt>. + * <div class="mdetail-params"><ul> + * <li><b><code>ROOFTOP</code></b> : <div class="sub-desc"><p> + * The returned result is a precise geocode for which we have location information accurate down to street address precision. + * </p></div></li> + * <li><b><code>RANGE_INTERPOLATED</code></b> : <div class="sub-desc"><p> + * The returned result reflects an approximation (usually on a road) interpolated between two precise points (such as intersections). Interpolated results are generally returned when rooftop geocodes are unavailable for a street address. + * </p></div></li> + * <li><b><code>GEOMETRIC_CENTER</code></b> : <div class="sub-desc"><p> + * The returned result is the geometric center of a result such as a polyline (for example, a street) or polygon (region). + * </p></div></li> + * <li><b><code>APPROXIMATE</code></b> : <div class="sub-desc"><p> + * The returned result is approximate. + * </p></div></li> + * </ul></div> + */ + geoErrorMsgAccuracy : 'The address provided has a low accuracy.<br><br>"{0}" Accuracy.<br><br>{1}', + /** + * @cfg {String} gmapType + * The type of map to display, generic options available are: 'map', 'panorama'. + * Defaults to <tt>'map'</tt>. + * More specific maps can be used by specifying the google map type: + * <div class="mdetail-params"><ul> + * <li><b><code>G_NORMAL_MAP</code></b> : <div class="sub-desc"><p> + * Displays the default road map view + * </p></div></li> + * <li><b><code>G_SATELLITE_MAP</code></b> : <div class="sub-desc"><p> + * Displays Google Earth satellite images + * </p></div></li> + * <li><b><code>G_HYBRID_MAP</code></b> : <div class="sub-desc"><p> + * Displays a mixture of normal and satellite views + * </p></div></li> + * <li><b><code>G_DEFAULT_MAP_TYPES</code></b> : <div class="sub-desc"><p> + * Contains an array of the above three types, useful for iterative processing. + * </p></div></li> + * <li><b><code>G_PHYSICAL_MAP</code></b> : <div class="sub-desc"><p> + * Displays a physical map based on terrain information. + * </p></div></li> + * <li><b><code>G_MOON_ELEVATION_MAP</code></b> : <div class="sub-desc"><p> + * Displays a shaded terrain map of the surface of the Moon, color-coded by altitude. + * </p></div></li> + * <li><b><code>G_MOON_VISIBLE_MAP</code></b> : <div class="sub-desc"><p> + * Displays photographic imagery taken from orbit around the moon. + * </p></div></li> + * <li><b><code>G_MARS_ELEVATION_MAP</code></b> : <div class="sub-desc"><p> + * Displays a shaded terrain map of the surface of Mars, color-coded by altitude. + * </p></div></li> + * <li><b><code>G_MARS_VISIBLE_MAP</code></b> : <div class="sub-desc"><p> + * Displays photographs taken from orbit around Mars. + * </p></div></li> + * <li><b><code>G_MARS_INFRARED_MAP</code></b> : <div class="sub-desc"><p> + * Displays a shaded infrared map of the surface of Mars, where warmer areas appear brighter and colder areas appear darker. + * </p></div></li> + * <li><b><code>G_SKY_VISIBLE_MAP</code></b> : <div class="sub-desc"><p> + * Displays a mosaic of the sky, as seen from Earth, covering the full celestial sphere. + * </p></div></li> + * </ul></div> + * Sample usage: + * <pre><code> + * gmapType: G_MOON_VISIBLE_MAP + * </code></pre> + */ + gmapType : 'map', + /** + * @cfg {Object} setCenter + * The initial center location of the map. The map needs to be centered before it can be used. + * A marker is not required to be specified. + * More markers can be added to the map using the <code>{@link #markers}</code> array. + * For example: + * <pre><code> +setCenter: { + geoCodeAddr: '4 Yawkey Way, Boston, MA, 02215-3409, USA', + marker: {title: 'Fenway Park'} +}, + +// or just specify lat/long +setCenter: { + lat: 42.345573, + lng: -71.098326 +} + * </code></pre> + */ + /** + * @cfg {Number} zoomLevel + * The zoom level to initialize the map at, generally between 1 (whole planet) and 40 (street). + * Also used as the zoom level for panoramas, zero specifies no zoom at all. + * Defaults to <tt>3</tt>. + */ + zoomLevel: 3, + /** + * @cfg {Number} yaw + * The Yaw, or rotational direction of the users perspective in degrees. Only applies to panoramas. + * Defaults to <tt>180</tt>. + */ + yaw: 180, + /** + * @cfg {Number} pitch + * The pitch, or vertical direction of the users perspective in degrees. + * Defaults to <tt>0</tt> (straight ahead). Valid values are between +90 (straight up) and -90 (straight down). + */ + pitch: 0, + /** + * @cfg {Boolean} displayGeoErrors + * True to display geocoding errors to the end user via a message box. + * Defaults to <tt>false</tt>. + */ + displayGeoErrors: false, + /** + * @cfg {Boolean} minGeoAccuracy + * The level to display an accuracy error below. Defaults to <tt>ROOFTOP</tt>. For additional information + * see <a href="http://code.google.com/apis/maps/documentation/reference.html#GGeoAddressAccuracy">here</a>. + */ + minGeoAccuracy: 'ROOFTOP', + /** + * @cfg {Array} mapConfOpts + * Array of strings representing configuration methods to call, a full list can be found + * <a href="http://code.google.com/apis/maps/documentation/reference.html#GMap2">here</a>. + * For example: + * <pre><code> + * mapConfOpts: ['enableScrollWheelZoom','enableDoubleClickZoom','enableDragging'], + * </code></pre> + */ + /** + * @cfg {Array} mapControls + * Array of strings representing map controls to initialize, a full list can be found + * <a href="http://code.google.com/apis/maps/documentation/reference.html#GControlImpl">here</a>. + * For example: + * <pre><code> + * mapControls: ['GSmallMapControl','GMapTypeControl','NonExistantControl'] + * </code></pre> + */ + /** + * @cfg {Array} markers + * Markers may be added to the map. Instead of specifying <code>lat</code>/<code>lng</code>, + * geocoding can be specified via a <code>geoCodeAddr</code> string. + * For example: + * <pre><code> +markers: [{ + //lat: 42.339641, + //lng: -71.094224, + // instead of lat/lng: + geoCodeAddr: '465 Huntington Avenue, Boston, MA, 02215-5597, USA', + marker: {title: 'Boston Museum of Fine Arts'}, + listeners: { + click: function(e){ + Ext.Msg.alert('Its fine', 'and its art.'); + } + } +},{ + lat: 42.339419, + lng: -71.09077, + marker: {title: 'Northeastern University'} +}] + * </code></pre> + */ + // private + mapDefined: false, + // private + mapDefinedGMap: false, + initComponent : function(){ + + this.addEvents( + /** + * @event mapready + * Fires when the map is ready for interaction + * @param {GMapPanel} this + * @param {GMap} map + */ + 'mapready', + /** + * @event apiready + * Fires when the Google Maps API is loaded + */ + 'apiready' + ); + + Ext.applyIf(this,{ + markers: [], + cache: { + marker: [], + polyline: [], + infowindow: [] + } + }); + + Ext.ux.GMapPanel.superclass.initComponent.call(this); + + if (window.google && window.google.maps){ + this.on('afterrender', this.apiReady, this); + }else{ + window.gmapapiready = Ext.Function.bind(this.apiReady,this); + this.buildScriptTag('http://maps.google.com/maps/api/js?sensor=false&callback=gmapapiready'); + } + + }, + apiReady : function(){ + + if (this.rendered){ + + Ext.defer(function(){ + if (this.gmapType === 'map'){ + this.gmap = new google.maps.Map(this.getEl().dom, {zoom:this.zoomLevel,mapTypeId: google.maps.MapTypeId.ROADMAP}); + this.mapDefined = true; + this.mapDefinedGMap = true; + } + + if (this.gmapType === 'panorama'){ + this.gmap = new GStreetviewPanorama(this.getEl().dom); + this.mapDefined = true; + } + + if (!this.mapDefined && this.gmapType){ + this.gmap = new google.maps.Map(this.getEl().dom, {zoom:this.zoomLevel,mapTypeId: google.maps.MapTypeId.ROADMAP}); + this.gmap.setMapTypeId(this.gmapType); + this.mapDefined = true; + this.mapDefinedGMap = true; + } + + google.maps.event.addListenerOnce(this.getMap(), 'tilesloaded', Ext.Function.bind(this.onMapReady, this)); + google.maps.event.addListener(this.getMap(), 'dragend', Ext.Function.bind(this.dragEnd, this)); + + + if (typeof this.setCenter === 'object') { + if (typeof this.setCenter.geoCodeAddr === 'string'){ + this.geoCodeLookup(this.setCenter.geoCodeAddr, this.setCenter.marker, false, true, this.setCenter.listeners); + }else{ + if (this.gmapType === 'map'){ + var point = new google.maps.LatLng(this.setCenter.lat,this.setCenter.lng); + this.getMap().setCenter(point, this.zoomLevel); + this.lastCenter = point; + } + if (typeof this.setCenter.marker === 'object' && typeof point === 'object') { + this.addMarker(point, this.setCenter.marker, this.setCenter.marker.clear); + } + } + if (this.gmapType === 'panorama'){ + this.getMap().setLocationAndPOV(new google.maps.LatLng(this.setCenter.lat,this.setCenter.lng), {yaw: this.yaw, pitch: this.pitch, zoom: this.zoomLevel}); + } + } + }, 200,this); // Ext.defer + + }else{ + this.on('afterrender', this.apiReady, this); + } + }, + // private + afterRender : function(){ + + var wh = this.ownerCt.getSize(); + Ext.applyIf(this, wh); + + Ext.ux.GMapPanel.superclass.afterRender.call(this); + + }, + // private + buildScriptTag: function(filename, callback) { + var script = document.createElement('script'), + head = document.getElementsByTagName("head")[0]; + script.type = "text/javascript"; + script.src = filename; + + return head.appendChild(script); + }, + // private + onMapReady : function(){ + + this.addMapControls(); + this.addOptions(); + + this.addMarkers(this.markers); + this.addMapListeners(); + + this.fireEvent('mapready', this, this.getMap()); + return this; + }, + // private + addMapListeners : function () { + if (this.maplisteners){ + Ext.iterate(this.maplisteners, function(key,val){ + google.maps.event.addListener(this.getMap(), key, Ext.Function.bind(val,this)); + },this); + } + }, + // private + onResize : function(w, h){ + + Ext.ux.GMapPanel.superclass.onResize.call(this, w, h); + + // check for the existance of the google map in case the onResize fires too early + if (typeof this.getMap() == 'object') { + google.maps.event.trigger(this.getMap(), 'resize'); + if (this.lastCenter){ + this.getMap().setCenter(this.lastCenter, this.zoomLevel); + } + } + + }, + // private + setSize : function(width, height, animate){ + + Ext.ux.GMapPanel.superclass.setSize.call(this, width, height, animate); + + // check for the existance of the google map in case setSize is called too early + if (Ext.isObject(this.getMap())) { + google.maps.event.trigger(this.getMap(), 'resize'); + if (this.lastCenter){ + this.getMap().setCenter(this.lastCenter, this.zoomLevel); + } + } + + }, + // private + dragEnd: function(){ + this.lastCenter = this.getMap().getCenter(); + }, + /** + * Returns the current google map which can be used to call Google Maps API specific handlers. + * @return {GMap} this + */ + getMap : function(){ + + return this.gmap; + + }, + /** + * Returns the maps center as a GLatLng object + * @return {GLatLng} this + */ + getCenter : function(){ + + return this.getMap().getCenter(); + + }, + /** + * Returns the maps center as a simple object + * @return {Object} this has lat and lng properties only + */ + getCenterLatLng : function(){ + + var ll = this.getCenter(); + return {lat: ll.lat(), lng: ll.lng()}; + + }, + /** + * Creates markers from the array that is passed in. Each marker must consist of at least + * <code>lat</code> and <code>lng</code> properties or a <code>geoCodeAddr</code>. + * @param {Array} markers an array of marker objects + */ + addMarkers : function(markers) { + if (Ext.isArray(markers)){ + for (var i = 0; i < markers.length; i++) { + if (markers[i]) { + if (typeof markers[i].geoCodeAddr == 'string') { + this.geoCodeLookup(markers[i].geoCodeAddr, markers[i].marker, false, markers[i].setCenter, markers[i].listeners); + } else { + var mkr_point = new google.maps.LatLng(markers[i].lat, markers[i].lng); + this.addMarker(mkr_point, markers[i].marker, false, markers[i].setCenter, markers[i].listeners); + } + } + } + } + + }, + /** + * Creates a single marker. + * @param {Object} point a GLatLng point + * @param {Object} marker a marker object consisting of at least lat and lng + * @param {Boolean} clear clear other markers before creating this marker + * @param {Boolean} center true to center the map on this marker + * @param {Object} listeners a listeners config + */ + addMarker : function(point, marker, clear, center, listeners){ + + Ext.applyIf(marker,{}); + + if (clear === true){ + this.clearMarkers(); + } + if (center === true) { + this.getMap().setCenter(point, this.zoomLevel) + this.lastCenter = point; + } + + var mark = new google.maps.Marker(Ext.apply(marker, { + position: point + })); + + if (marker.infoWindow){ + this.createInfoWindow(marker.infoWindow, point, mark); + } + + this.cache.marker.push(mark); + mark.setMap(this.getMap()); + + if (typeof listeners === 'object'){ + for (evt in listeners) { + google.maps.event.addListener(mark, evt, listeners[evt]); + } + } + + return mark; + + }, + /** + * Creates a single polyline. + * @param {Array} points an array of polyline points + * @param {Object} linestyle an object defining the line style to use + */ + addPolyline : function(points, linestyle){ + + var plinepnts = new google.maps.MVCArray, pline, linestyle = linestyle ? linestyle : { + strokeColor: '#FF0000', + strokeOpacity: 1.0, + strokeWeight: 2 + }; + + Ext.each(points, function(point){ + plinepnts.push(new google.maps.LatLng(point.lat, point.lng)); + }, this); + + var pline = new google.maps.Polyline(Ext.apply({ + path: plinepnts + },linestyle)); + + this.cache.polyline.push(pline); + + pline.setMap(this.getMap()); + + }, + /** + * Creates an Info Window. + * @param {Object} inwin an Info Window configuration + * @param {GLatLng} point the point to show the Info Window at + * @param {GMarker} marker a marker to attach the Info Window to + */ + createInfoWindow : function(inwin, point, marker){ + + var me = this, infoWindow = new google.maps.InfoWindow({ + content: inwin.content, + position: point + }); + + if (marker) { + google.maps.event.addListener(marker, 'click', function(){ + me.hideAllInfoWindows(); + infoWindow.open(me.getMap()); + }); + } + + this.cache.infowindow.push(infoWindow); + + return infoWindow; + + }, + // private + hideAllInfoWindows : function(){ + for (var i = 0; i < this.cache.infowindow.length; i++) { + this.cache.infowindow[i].close(); + } + }, + // private + clearMarkers : function(){ + + this.hideAllInfoWindows(); + this.hideMarkers(); + + }, + // private + hideMarkers : function(){ + Ext.each(this.cache.marker, function(mrk){ + mrk.setMap(null); + }); + }, + // private + showMarkers : function(){ + Ext.each(this.cache.marker, function(mrk){ + mrk.setMap(this.getMap()); + },this); + }, + // private + addMapControls : function(){ + + if (this.gmapType === 'map') { + if (Ext.isArray(this.mapControls)) { + for(i=0;i<this.mapControls.length;i++){ + //this.addMapControl(this.mapControls[i]); + } + }else if(typeof this.mapControls === 'string'){ + //this.addMapControl(this.mapControls); + }else if(typeof this.mapControls === 'object'){ + //this.getMap().add_control(this.mapControls); + } + } + + }, + /** + * Adds a GMap control to the map. + * @param {String} mc a string representation of the control to be instantiated. + */ + addMapControl : function(mc){ + + var mcf = window[mc]; + if (typeof mcf === 'function') { + //this.getMap().addControl(new mcf()); + } + + }, + // private + addOptions : function(){ + + if (Ext.isArray(this.mapConfOpts)) { + var mc; + for(i=0;i<this.mapConfOpts.length;i++){ + //this.addOption(this.mapConfOpts[i]); + } + }else if(typeof this.mapConfOpts === 'string'){ + //this.addOption(this.mapConfOpts); + } + + }, + /** + * Adds a GMap option to the map. + * @param {String} mo a string representation of the option to be instantiated. + */ + addOption : function(mo){ + + var mof = this.getMap()[mo]; + if (typeof mof === 'function') { + this.getMap()[mo](); + } + + }, + /** + * Looks up and address and optionally add a marker, center the map to this location, or + * clear other markers. Sample usage: + * <pre><code> +buttons: [ + { + text: 'Fenway Park', + handler: function(){ + var addr = '4 Yawkey Way, Boston, MA, 02215-3409, USA'; + Ext.getCmp('my_map').geoCodeLookup(addr, undefined, false, true, undefined); + } + },{ + text: 'Zoom Fenway Park', + handler: function(){ + Ext.getCmp('my_map').zoomLevel = 19; + var addr = '4 Yawkey Way, Boston, MA, 02215-3409, USA'; + Ext.getCmp('my_map').geoCodeLookup(addr, undefined, false, true, undefined); + } + },{ + text: 'Low Accuracy', + handler: function(){ + Ext.getCmp('my_map').geoCodeLookup('Paris, France', undefined, false, true, undefined); + } + },{ + + text: 'Bogus Address', + handler: function(){ + var addr = 'Some Fake, Address, For, Errors'; + Ext.getCmp('my_map').geoCodeLookup(addr, undefined, false, true, undefined); + } + } +] + * </code></pre> + * @param {String} addr the address to lookup. + * @param {Object} marker the marker to add (optional). + * @param {Boolean} clear clear other markers before creating this marker + * @param {Boolean} center true to set this point as the center of the map. + * @param {Object} listeners a listeners config + */ + geoCodeLookup : function(addr, marker, clear, center, listeners) { + + if (!this.geocoder) { + this.geocoder = new google.maps.Geocoder(); + } + this.geocoder.geocode({ + address: addr + }, Ext.Function.bind(this.addAddressToMap, this, [addr, marker, clear, center, listeners], true)); + + }, + // private + centerOnClientLocation : function(){ + this.getClientLocation(function(loc){ + var point = new google.maps.LatLng(loc.latitude,loc.longitude); + this.getMap().setCenter(point, this.zoomLevel); + this.lastCenter = point; + }); + }, + // private + getClientLocation : function(fn, errorFn){ + if (!errorFn) { + errorFn = Ext.emptyFn; + } + if (!this.clientGeo) { + this.clientGeo = google.gears.factory.create('beta.geolocation'); + } + geo.getCurrentPosition(Ext.Function.bind(fn, this), errorFn); + }, + // private + addAddressToMap : function(response, status, addr, marker, clear, center, listeners){ + if (!response || status !== 'OK') { + this.respErrorMsg(status); + }else{ + var place = response[0].geometry.location, + accuracy = this.getLocationTypeInfo(response[0].geometry.location_type,'level'), + reqAccuracy = this.getLocationTypeInfo(this.minGeoAccuracy,'level'); + if (accuracy === 0) { + this.geoErrorMsg(this.geoErrorTitle, this.geoErrorMsgUnable); + }else{ + if (accuracy < reqAccuracy) { + this.geoErrorMsg(this.geoErrorTitle, Ext.String.format(this.geoErrorMsgAccuracy, response[0].geometry.location_type, this.getLocationTypeInfo(response[0].geometry.location_type,'msg'))); + }else{ + point = new google.maps.LatLng(place.lat(),place.lng()); + if (center){ + this.getMap().setCenter(point, this.zoomLevel); + this.lastCenter = point; + } + if (typeof marker === 'object') { + if (!marker.title){ + marker.title = response.formatted_address; + } + var mkr = this.addMarker(point, marker, clear, false, listeners); + if (marker.callback){ + marker.callback.call(this, mkr, point); + } + } + } + } + } + + }, + // private + geoErrorMsg : function(title,msg){ + if (this.displayGeoErrors) { + Ext.MessageBox.alert(title,msg); + } + }, + // private + respErrorMsg : function(code){ + Ext.each(this.respErrors, function(obj){ + if (code == obj.code){ + Ext.MessageBox.alert(this.respErrorTitle, obj.msg); + } + }, this); + }, + // private + getLocationTypeInfo: function(location_type,property){ + var val = 0; + Ext.each(this.locationTypes, function(itm){ + if (itm.code === location_type){ + val = itm[property]; + } + }); + return val; + } +}); +</script> <script type="text/javascript"> Ext.onReady(function() { @@ -22,7 +761,7 @@ Ext.onReady(function() { Ext.define('Position', { extend: 'Ext.data.Model', fields: [ - {name: 'device_id', type: 'int'}, + {name: 'device_id', type: 'int'}, {name: 'time', type: 'date'}, {name: 'valid', type: 'boolean'}, {name: 'latitude', type: 'float'}, @@ -40,9 +779,15 @@ Ext.onReady(function() { id: 'devices', model: 'Device', fields: ['id', 'imei'], + autoSync: true, proxy: { type: 'ajax', - url: devicesUrl, + api: { + create: devicesUrl + '?action=create', + read: devicesUrl, + update: devicesUrl + '?action=update', + destroy: devicesUrl + '?action=destroy' + }, reader: { type: 'json', root: 'results' @@ -73,7 +818,10 @@ Ext.onReady(function() { } }); - console.log(positions); + var map = Ext.create('Ext.ux.GMapPanel', { + id: 'gmap', + setCenter: {lat: 0, lng: 0} + }); var devicesPanel = Ext.create('Ext.grid.Panel', { title: 'Devices', @@ -98,7 +846,10 @@ Ext.onReady(function() { id: 'device_add', text: 'Add', handler : function() { - Ext.Msg.prompt('Add', 'Device IMEI:', function() { + Ext.Msg.prompt('Add', 'Device IMEI:', function(btn, text) { + if (btn == 'ok') { + devices.add({imei: text}); + } }); } }, @@ -107,7 +858,10 @@ Ext.onReady(function() { text: 'Remove', disabled: true, handler : function() { - Ext.Msg.confirm('Remove', 'Are you sure to remove item?', function() { + Ext.Msg.confirm('Remove', 'Are you sure to remove item?', function(btn) { + if (btn == 'yes') { + devices.remove(devicesPanel.getSelectionModel().getLastSelected()); + } }); } }, @@ -116,7 +870,10 @@ Ext.onReady(function() { text: 'Edit', disabled: true, handler : function() { - Ext.Msg.prompt('Edit', 'Device IMEI:', function() { + Ext.Msg.prompt('Edit', 'Device IMEI:', function(btn, text) { + if (btn == 'ok') { + devicesPanel.getSelectionModel().getLastSelected().set('imei', text); + } }, this, false, devicesPanel.getSelectionModel().getLastSelected().get('imei')); } } @@ -182,21 +939,28 @@ Ext.onReady(function() { {header: 'Speed', dataIndex: 'speed'}, {header: 'Course', dataIndex: 'course'}, {header: 'Power', dataIndex: 'power'} - ] + ], + listeners: { + selectionchange: function(sender, selected, eOpts) { + if (selected.length != 0) { + var lat = positionsPanel.getSelectionModel().getLastSelected().get('latitude'); + var lng = positionsPanel.getSelectionModel().getLastSelected().get('longitude'); + var point = new google.maps.LatLng(lat, lng); + map.addMarker(point, {lat: lat, lng: lng}, true, true); + } else { + map.clearMarkers(); // private? + } + } + } }); - /*var map = Ext.create('Ext.ux.GMapPanel', { - id: 'gmap', - setCenter: {lat: 0, lng: 0} - });*/ - var mapPanel = Ext.create('Ext.panel.Panel', { title: 'Map', region: 'center', - margins: {top: 5, bottom: 0, right: 5, left: 0}/*, + margins: {top: 5, bottom: 0, right: 5, left: 0}, layout: 'fit', - items: [map]*/ + items: [map] }); Ext.create('Ext.container.Viewport', { diff --git a/src/org/traccar/Server.java b/src/org/traccar/Server.java index fda5e6ad3..654d32321 100644 --- a/src/org/traccar/Server.java +++ b/src/org/traccar/Server.java @@ -86,15 +86,19 @@ public class Server { initAvl08Server(properties); // Initialize web server - Integer port = Integer.valueOf(properties.getProperty("http.port", "8082")); - webServer = new WebServer(port, dataManager); + if (Boolean.valueOf(properties.getProperty("http.enable"))) { + Integer port = Integer.valueOf(properties.getProperty("http.port", "8082")); + webServer = new WebServer(port, dataManager); + } } /** * Start */ public void start() { - webServer.start(); + if (webServer != null) { + webServer.start(); + } for (Object server: serverList) { ((TrackerServer) server).start(); } @@ -107,7 +111,9 @@ public class Server { for (Object server: serverList) { ((TrackerServer) server).stop(); } - webServer.stop(); + if (webServer != null) { + webServer.stop(); + } } /** diff --git a/src/org/traccar/helper/NamedParameterStatement.java b/src/org/traccar/helper/NamedParameterStatement.java index c86217182..e898af2d3 100644 --- a/src/org/traccar/helper/NamedParameterStatement.java +++ b/src/org/traccar/helper/NamedParameterStatement.java @@ -30,12 +30,12 @@ public class NamedParameterStatement { * Index mapping */ private final Map indexMap; - + /** * Query string */ private final String parsedQuery; - + /** * Database connection */ @@ -106,7 +106,7 @@ public class NamedParameterStatement { return parsedQuery.toString(); } - + public void prepare() throws SQLException { try { if (statement == null) { @@ -134,6 +134,13 @@ public class NamedParameterStatement { } /** + * Return generated keys + */ + public ResultSet getGeneratedKeys() throws SQLException { + return statement.getGeneratedKeys(); + } + + /** * Immediately closes the statement */ public void close() throws SQLException { diff --git a/src/org/traccar/http/WebServer.java b/src/org/traccar/http/WebServer.java index d42f8fe2c..53cdb2966 100644 --- a/src/org/traccar/http/WebServer.java +++ b/src/org/traccar/http/WebServer.java @@ -20,6 +20,8 @@ import java.io.InputStream; import java.io.OutputStream; import java.io.PrintWriter; import java.util.Iterator; +import java.util.regex.Matcher; +import java.util.regex.Pattern; import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; @@ -79,28 +81,65 @@ public class WebServer { out.flush(); } + private Device parseDevice(String json) { + + Pattern pattern = Pattern.compile("\\{\"id\":(\\d+),\"imei\":\"(.*)\"\\}"); + Matcher parser = pattern.matcher(json); + if (parser.matches()) { + Device device = new Device(); + device.setId(Long.valueOf(parser.group(1))); + device.setImei(parser.group(2)); + return device; + } + + return null; + } + + // TODO: separate method into small parts public void handleData(String target,Request baseRequest,HttpServletRequest request,HttpServletResponse response) throws IOException, ServletException { response.setContentType("application/json"); PrintWriter out = response.getWriter(); - out.print("{'success':true,'results':["); try { if (target.equals("/devices.json")) { - Iterator<Device> i = dataManager.getDevices().iterator(); - while (i.hasNext()) { - Device device = i.next(); + String action = request.getParameter("action"); + if (action == null) { + Iterator<Device> i = dataManager.getDevices().iterator(); + out.print("{'success':true,'results':["); + while (i.hasNext()) { + Device device = i.next(); + out.format("{'id':%d,'imei':'%s'}", + device.getId(), + device.getImei()); + if (i.hasNext()) out.print(","); + } + } else if (action.equals("create")) { + Device device = parseDevice(request.getReader().readLine()); + dataManager.addDevice(device); + out.print("{'success':true,'results':["); + out.format("{'id':%d,'imei':'%s'}", + device.getId(), + device.getImei()); + } else if (action.equals("update")) { + Device device = parseDevice(request.getReader().readLine()); + dataManager.updateDevice(device); + out.print("{'success':true,'results':["); out.format("{'id':%d,'imei':'%s'}", device.getId(), device.getImei()); - if (i.hasNext()) out.print(","); + } else if (action.equals("destroy")) { + Device device = parseDevice(request.getReader().readLine()); + dataManager.removeDevice(device); + out.print("{'success':true,'results':["); } } else if (target.equals("/positions.json")) { + out.print("{'success':true,'results':["); String deviceId = request.getParameter("deviceId"); if (deviceId != null) { Iterator<Position> i = dataManager.getPositions(Long.valueOf(deviceId)).iterator(); @@ -120,10 +159,10 @@ public class WebServer { } } catch (Exception error) { + out.print("{'success':false,'results':["); System.out.println(error.getMessage()); } - //" {'id': 1, 'imei': '123456789012345'} ]}"); out.print("]}"); out.flush(); } @@ -138,7 +177,7 @@ public class WebServer { { if (target.equals("/") || target.equals("/index.html")) { handleIndex(target, baseRequest, request, response); - } else if (target.matches("/favicon.ico")) { + } else if (target.equals("/favicon.ico")) { handleIcon(target, baseRequest, request, response); } else if (target.matches("/.+\\.json")) { handleData(target, baseRequest, request, response); diff --git a/src/org/traccar/model/DataManager.java b/src/org/traccar/model/DataManager.java index d0f290962..70cf63380 100644 --- a/src/org/traccar/model/DataManager.java +++ b/src/org/traccar/model/DataManager.java @@ -27,8 +27,8 @@ public interface DataManager { */ public List<Device> getDevices() throws Exception; public void addDevice(Device device) throws Exception; - public void addUpdate(Device device) throws Exception; - public void addRemove(Device device) throws Exception; + public void updateDevice(Device device) throws Exception; + public void removeDevice(Device device) throws Exception; public Device getDeviceByImei(String imei) throws Exception; /** diff --git a/src/org/traccar/model/DatabaseDataManager.java b/src/org/traccar/model/DatabaseDataManager.java index 7d90ac194..c5492bc8a 100644 --- a/src/org/traccar/model/DatabaseDataManager.java +++ b/src/org/traccar/model/DatabaseDataManager.java @@ -35,16 +35,13 @@ public class DatabaseDataManager implements DataManager { * Database statements */ private NamedParameterStatement queryGetDevices; + private NamedParameterStatement queryAddDevice; + private NamedParameterStatement queryUpdateDevice; + private NamedParameterStatement queryRemoveDevice; + private NamedParameterStatement queryGetPositions; private NamedParameterStatement queryAddPosition; /** - * Devices cache - */ - private Map devices; - private Calendar devicesLastUpdate; - private Long devicesRefreshDelay; - - /** * Initialize database */ private void initDatabase(Properties properties) @@ -70,9 +67,24 @@ public class DatabaseDataManager implements DataManager { String password = properties.getProperty("database.password"); AdvancedConnection connection = new AdvancedConnection(url, user, password); - String query = properties.getProperty("database.selectDevice"); + // Load statements from configuration + String query; + + query = properties.getProperty("database.selectDevice"); queryGetDevices = new NamedParameterStatement(connection, query); + query = properties.getProperty("database.insertDevice"); + queryAddDevice = new NamedParameterStatement(connection, query); + + query = properties.getProperty("database.updateDevice"); + queryUpdateDevice = new NamedParameterStatement(connection, query); + + query = properties.getProperty("database.deleteDevice"); + queryRemoveDevice = new NamedParameterStatement(connection, query); + + query = properties.getProperty("database.selectPosition"); + queryGetPositions = new NamedParameterStatement(connection, query); + query = properties.getProperty("database.insertPosition"); queryAddPosition = new NamedParameterStatement(connection, query); } @@ -93,9 +105,46 @@ public class DatabaseDataManager implements DataManager { return deviceList; } - public void addDevice(Device device) {} // TODO: implement - public void addUpdate(Device device) {} // TODO: implement - public void addRemove(Device device) {} // TODO: implement + public synchronized void addDevice(Device device) throws SQLException { + + queryAddDevice.prepare(); + queryAddDevice.setString("imei", device.getImei()); + queryAddDevice.executeUpdate(); + + // Find generated id + ResultSet result = queryAddDevice.getGeneratedKeys(); + if (result.next()) { + device.setId(result.getLong(1)); + } + + devices = null; + } + + public synchronized void updateDevice(Device device) throws SQLException { + + queryUpdateDevice.prepare(); + queryUpdateDevice.setLong("id", device.getId()); + queryUpdateDevice.setString("imei", device.getImei()); + queryUpdateDevice.executeUpdate(); + + devices = null; + } + + public synchronized void removeDevice(Device device) throws SQLException { + + queryRemoveDevice.prepare(); + queryRemoveDevice.setLong("id", device.getId()); + queryRemoveDevice.executeUpdate(); + + devices = null; + } + + /** + * Devices cache + */ + private Map devices; + private Calendar devicesLastUpdate; + private Long devicesRefreshDelay; public Device getDeviceByImei(String imei) throws SQLException { @@ -110,20 +159,26 @@ public class DatabaseDataManager implements DataManager { return (Device) devices.get(imei); } - public List<Position> getPositions(Long deviceId) { // TODO: implement + public synchronized List<Position> getPositions(Long deviceId) throws SQLException { List<Position> positionList = new LinkedList(); - Position p = new Position(); - p.setDeviceId(new Long(1)); - p.setTime(new Date()); - p.setValid(true); - p.setLatitude(1.0); - p.setLongitude(1.0); - p.setSpeed(1.0); - p.setCourse(1.0); - - positionList.add(p); + queryGetPositions.prepare(); + queryGetPositions.setLong("device_id", deviceId); + ResultSet result = queryGetPositions.executeQuery(); + while (result.next()) { + // TODO: include other parameters + Position position = new Position(); + position.setDeviceId(result.getLong("device_id")); + position.setTime(result.getTimestamp("time")); + position.setValid(result.getBoolean("valid")); + position.setLatitude(result.getDouble("latitude")); + position.setLongitude(result.getDouble("longitude")); + position.setSpeed(result.getDouble("speed")); + position.setCourse(result.getDouble("course")); + position.setPower(result.getDouble("power")); + positionList.add(position); + } return positionList; } @@ -144,6 +199,8 @@ public class DatabaseDataManager implements DataManager { queryAddPosition.setString("extended_info", position.getExtendedInfo()); queryAddPosition.executeUpdate(); + + // TODO: probably return row id } } |