Changeset 12698 for extensions/rv_gmaps


Ignore:
Timestamp:
Dec 6, 2011, 5:42:24 AM (12 years ago)
Author:
rvelices
Message:

rv_gmaps towards full maps api v3 migration (still to test)

Location:
extensions/rv_gmaps/trunk
Files:
6 edited

Legend:

Unmodified
Added
Removed
  • extensions/rv_gmaps/trunk/map.php

    r8304 r12698  
    8484}
    8585else
    86   $template->assign('MAP_MARKER_ICON_JS', 'return G_DEFAULT_ICON;');
     86  $template->assign('MAP_MARKER_ICON_JS', 'return {}');
    8787
    8888$template->pparse('map');
  • extensions/rv_gmaps/trunk/map_data.php

    r7388 r12698  
    6363function jsgm_position( $position )
    6464{
    65   return 'new GLatLng(' . $position['lat'] . ',' . $position['lon'] . ')';
     65  return 'new google.maps.LatLng(' . $position['lat'] . ',' . $position['lon'] . ')';
    6666}
    6767
    6868function jsgm_bounds( $bounds )
    6969{
    70   return 'new GLatLngBounds(' . jsgm_position(bounds_sw($bounds)) . ',' . jsgm_position(bounds_ne($bounds)) . ')';
     70  return 'new google.maps.LatLngBounds(' . jsgm_position(bounds_sw($bounds)) . ',' . jsgm_position(bounds_ne($bounds)) . ')';
    7171}
    7272
  • extensions/rv_gmaps/trunk/template/data_handler.js

    r8724 r12698  
    33{
    44        this._map = map;
     5        this._infoWindow = new google.maps.InfoWindow();
    56        this.options = jQuery.fn.extend(
    67                {
    7                         icon: G_DEFAULT_ICON,
     8                        markerOptions: {},
    89                        show_all_img_src: null
    910                }
    1011                , opts || {} );
    1112
    12         google.maps.Event.addListener( map, "infowindowclose", function() {map.getInfoWindow().pwgMarker=null;} );
     13        google.maps.event.addListener( map, "infowindowclose", function() {map.getInfoWindow().pwgMarker=null;} );
    1314}
    1415
     
    1617
    1718_map: null,
     19_infoWindow: null,
    1820options: {},
    1921_markers: [],
     
    6062        {
    6163                changed = false;
    62                 if (this._markers.length>0 && !this._markers[0].getLatLng().equals( data.image_clusters[0].position ) )
     64                if (this._markers.length>0 && !this._markers[0].getPosition().equals( data.image_clusters[0].position ) )
    6365                        changed=true;
    6466        }
     
    6769        {
    6870                var newMarkers = [];
    69                 var infoWindowMarker = this._map.getInfoWindow().pwgMarker;
     71                var infoWindowMarker = this._infoWindow.pwgMarker;
    7072
    7173                for (i=0; i<data.image_clusters.length; i++)
     
    8284                        if (marker && marker==infoWindowMarker)
    8385                        {
    84                                 this._map.removeOverlay( marker );
    85                                 google.maps.Event.clearListeners(marker, "click" );
    86                                 google.maps.Event.clearListeners(marker, "dblclick" );
    87                                 this._map.getInfoWindow().pwgMarker = infoWindowMarker = null;
    88                                 if (document.is_debug) google.maps.Log.write('removed marker with infoWindow');
     86        marker.setMap(null);
     87                                google.maps.event.clearListeners(marker, "click" );
     88                                google.maps.event.clearListeners(marker, "dblclick" );
     89                                this._infoWindow.pwgMarker = infoWindowMarker = null;
     90                                if (document.is_debug) glog('removed marker with infoWindow');
    8991                                marker = this._markers.pop();
    9092                        }
     
    9294                        if (!marker)
    9395                        {
    94                                 marker = new google.maps.Marker( cluster.position, {title:  theTitle, icon: this.options.icon } );
    95                                 google.maps.Event.addListener( marker, "click", pwgBind(this, this._onMarkerClick, marker) );
    96                                 google.maps.Event.addListener( marker, "dblclick", pwgBind(this, this._onMarkerDblClick, marker) );
    97                                 this._map.addOverlay( marker );
     96                                marker = new google.maps.Marker(this.options.markerOptions);
     97        marker.setPosition(cluster.position);
     98        marker.setTitle(theTitle);
     99                                google.maps.event.addListener( marker, "click", pwgBind(this, this._onMarkerClick, marker) );
     100                                google.maps.event.addListener( marker, "dblclick", pwgBind(this, this._onMarkerDblClick, marker) );
     101        marker.setMap(this._map);
    98102                        }
    99103                        else
    100104                        {
    101105                                marker.currentImageIndex=0;
    102                                 marker.setLatLng( cluster.position );
    103                                 // changing the marker title is undocumented so we hack it
    104                                 if (!this.hack)
    105                                 {
    106                                         this.hack = {};
    107                                         for (var prop in marker)
    108                                         {
    109                                                 if ( typeof(marker[prop])!='object') continue;
    110                                                 if (!this.hack.markerHtmlElemWithTitle )
    111                                                 {
    112                                                         try {
    113                                                                 if (eval("typeof marker." + prop + "[0].src") == "string" && eval("typeof marker." + prop + "[0].title") == "string" )
    114                                                                         this.hack.markerHtmlElemWithTitle = prop;
    115                                                         }
    116                                                         catch (e) {}
    117                                                 }
    118                                                 if (!this.hack.markerOptions)
    119                                                         try {
    120                                                                 if ( eval("typeof marker."+prop+".title")=="string" && eval("typeof marker."+prop+".src")=="undefined")
    121                                                                         this.hack.markerOptions = prop;
    122                                                         }
    123                                                         catch (e) {}
    124                                         }
    125                                 }
    126                                 //undocumented marker.K , marker.ch and marker.jb and marker.l
    127                                 if (this.hack.markerOptions)
    128                                         eval( 'marker.'+this.hack.markerOptions+'.title=theTitle');
    129                                 if (this.hack.markerHtmlElemWithTitle)
    130                                         eval( 'marker.'+this.hack.markerHtmlElemWithTitle+'[0].title=theTitle');
     106                                marker.setPosition( cluster.position );
     107                                marker.setTitle(theTitle);
     108                                marker.setMap(this._map);
    131109                        }
    132110
     
    143121                for (i=0; i<this._markers.length; i++)
    144122                {
    145                         this._map.removeOverlay( this._markers[i] );
    146                         google.maps.Event.clearListeners(this._markers[i], "click" );
    147                         google.maps.Event.clearListeners(this._markers[i], "dblclick" );
     123                        this._markers[i].setMap(null);
     124                        google.maps.event.clearListeners(this._markers[i], "click" );
     125                        google.maps.event.clearListeners(this._markers[i], "dblclick" );
    148126                }
    149127
     
    158136_onMarkerClick: function( marker )
    159137{
    160         if (this._map.getInfoWindow().pwgMarker == marker )
     138        if (this._infoWindow.pwgMarker == marker )
    161139                return; // already open
    162140        var content = "";
     
    182160        content += '<div id="pwgImageDetail">' + this.buildCurrentPictureHtml( marker ) + '</div>';
    183161
    184         marker.openInfoWindowHtml( content );
     162        this._infoWindow.setContent( content );
     163        this._infoWindow.setPosition( marker.getPosition() );
     164        this._infoWindow.open( this._map );
    185165
    186166        // bind to next / prev a little later because sometimes the nodes are not immediately created
     
    193173_onMarkerDblClick: function( marker )
    194174{
    195         this._map.setCenter( marker.pwg.bounds.getCenter(), this._map.getBoundsZoomLevel( marker.pwg.bounds ) );
     175        this._map.fitBounds( marker.pwg.bounds );
    196176},
    197177
     
    228208        clearTimeout(this._timerBindPictureNavigation);
    229209        this._timerBindPictureNavigation = null;
    230         this._map.getInfoWindow().pwgMarker = marker;
     210        this._infoWindow.pwgMarker = marker;
    231211        for (var i=0; i< this._navHtmlIds.length; i++)
    232212        {
    233213                var elt = document.getElementById( this._navHtmlIds[i] );
    234214                if (elt)
    235                         google.maps.Event.addDomListener(elt, "click", pwgBind(this, this._onPictureNavigate, marker, i) );
     215                        google.maps.event.addDomListener(elt, "click", pwgBind(this, this._onPictureNavigate, marker, i) );
    236216        }
    237217},
  • extensions/rv_gmaps/trunk/template/data_loader.js

    r8221 r12698  
    44        this.options = jQuery.fn.extend(
    55                {
    6                         reload_data_timeout: 1800,
    7                         rectangle_of_confusion: G_DEFAULT_ICON.iconSize
     6                        reload_data_timeout: 500,
     7                        rectangle_of_confusion: new google.maps.Size(32,16)
    88                }
    99                , opts || {} );
     
    3737{
    3838        this._urlMapData = urlMapData;
    39         google.maps.Event.bind( this._map, "movestart", this, this.clearTimerReloadData );
    40         google.maps.Event.bind( this._map, "moveend", this, this._onMapMoveEnd );
    41         this._loadData();
     39        google.maps.event.bind( this._map, "movestart", this, this.clearTimerReloadData );
     40        google.maps.event.bind( this._map, "idle", this, this._onIdle );
     41        //this._loadData();
    4242},
    4343
     
    5959},
    6060
    61 _onMapMoveEnd: function()
     61_onIdle: function()
    6262{
    6363        this.clearTimerReloadData();
     
    7878
    7979        var latRange = bounds.toSpan().lat();
    80         var latPrec = latRange * this.options.rectangle_of_confusion.height / this._map.getSize().height;
     80        var latPrec = latRange * this.options.rectangle_of_confusion.height / this._map.getDiv().offsetHeight;
    8181
    8282        var lonRange = bounds.toSpan().lng();
    83         var lonPrec = ( lonRange>=0 ? lonRange : 360-lonRange )* this.options.rectangle_of_confusion.width / this._map.getSize().width;
     83        var lonPrec = ( lonRange>=0 ? lonRange : 360-lonRange )* this.options.rectangle_of_confusion.width / this._map.getDiv().offsetWidth;
    8484
    8585        if ( this._previousLoadDataReq.box!=null )
    8686        { // not the first time
    87                 if ( this._previousLoadDataReq.box.containsBounds( bounds ) )
     87                if ( this._previousLoadDataReq.box.contains( bounds.getNorthEast() )
     88                                        && this._previousLoadDataReq.box.contains( bounds.getSouthWest() ))
    8889                {
    8990                        if ( this._previousLoadDataReq.resultBounds == null )
     
    109110
    110111        var nd=0, sd=0, ed=0, wd=0;
    111         if ( !bounds.isFullLat() )
     112        /*if ( !bounds.isFullLat() )*/
    112113        {
    113114                nd = latRange*12/100;
    114115                sd = latRange*4/100;
    115116        }
    116         if ( !bounds.isFullLng() )
     117        /*if ( !bounds.isFullLng() )*/
    117118        {
    118119                ed = lonRange*9/100;
     
    130131
    131132        if (document.is_debug) {
    132                 google.maps.Log.write("sd="+sd+" wd="+wd+" nd="+nd+" ed="+ed);
    133                 google.maps.Log.write( "bounds: " + this._map.getBounds().getSouthWest().toUrlValue() + " " + this._map.getBounds().getNorthEast().toUrlValue() +"; zoom: "+this._map.getZoom() +"; size: "+this._map.getSize().toString() +"; c: "+this._map.getCenter().toUrlValue() );
    134                 google.maps.Log.writeUrl( url );
     133                glog("sd="+sd+" wd="+wd+" nd="+nd+" ed="+ed);
     134                glog( url );
    135135        }
    136136
     
    141141
    142142        try {
    143                 google.maps.Event.trigger( this, "dataloading" );
     143                google.maps.event.trigger( this, "dataloading" );
    144144                jQuery.ajax( {
    145145                        url: url,
     
    151151                this._dataLoading = false;
    152152                this._previousLoadDataReq.box=null;
    153                 google.maps.Event.trigger( this, "dataloadfailed", 600, e );
     153                google.maps.event.trigger( this, "dataloadfailed", 600, e );
    154154        }
    155155},
     
    164164                        throw new Error( "DATA DECODING ERROR" );
    165165                this._previousLoadDataReq.resultBounds = resp.bounds;
    166                 if (document.is_debug && resp.debug) google.maps.Log.write( resp.debug );
    167                 google.maps.Event.trigger( this, "dataloaded", resp );
     166                if (document.is_debug && resp.debug) glog( resp.debug );
     167                google.maps.event.trigger( this, "dataloaded", resp );
    168168        }
    169169        catch (e)       {
    170170                this._previousLoadDataReq.box=null;
    171                 google.maps.Event.trigger( this, "dataloadfailed", responseCode, e );
     171                google.maps.event.trigger( this, "dataloadfailed", responseCode, e );
    172172                var s = e.message;
    173173                s += '\n' + data.substr(0,1000);
     
    182182{
    183183        try {
    184                 google.maps.Event.trigger( this, "dataloadfailed", textStatus + xhr.status, exc );
     184                google.maps.event.trigger( this, "dataloadfailed", textStatus + xhr.status, exc );
    185185        }
    186186        catch (e) {
  • extensions/rv_gmaps/trunk/template/map.tpl

    r12367 r12698  
    1 <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
     1<!DOCTYPE html>
    22<html xmlns="http://www.w3.org/1999/xhtml" xmlns:v="urn:schemas-microsoft-com:vml">
    33<head>
    44<meta http-equiv="content-type" content="text/html; charset={$CONTENT_ENCODING}" />
     5<meta name="viewport" content="initial-scale=1.0, user-scalable=no" />
    56<meta name="robots" content="noindex,nofollow" />
    67<title>{$GALLERY_TITLE}</title>
    78
    8 <script src="http://maps.google.com/maps?file=api&amp;v=2&amp;key={$GMAPS_API_KEY}&amp;hl={$lang_info.code}" type="text/javascript"></script>
     9<script src="http://maps.googleapis.com/maps/api/js?sensor=false&amp;language={$lang_info.code}" type="text/javascript"></script>
    910
    1011{combine_script id='jquery' load='header' path='http://ajax.googleapis.com/ajax/libs/jquery/1.6.2/jquery.min.js'}
     
    4647        document._window = window;
    4748}
    48  
     49
     50function glog(msg) {
     51        if (console)
     52        {
     53                console.debug(msg + " b="+map.getBounds().toUrlValue() + " c="+map.getCenter().toUrlValue() + " z="+map.getZoom() );
     54        }
     55}
     56
    4957function load()
    5058{
    51         if (!GBrowserIsCompatible())
    52         return;
    53 
    54         var mapElement = document.getElementById("map");
    55         map = new google.maps.Map2( mapElement );
    56 
    57         map.addMapType( G_PHYSICAL_MAP );
    58         map.addControl(new google.maps.LargeMapControl3D());
    59         map.addControl(new google.maps.HierarchicalMapTypeControl());
    60         map.addControl(new google.maps.ScaleControl());
    61 
    62         pwgPageLinker = new PageLinker(map, "aLinkToThisPage" );
    63         if ( !map.isLoaded() )
     59        var mapOptions = {
     60                mapTypeId: google.maps.MapTypeId.ROADMAP,
     61                overviewMapControl: true
     62        }
     63
     64        if (1/*!PageLinker.url2Map(mapOptions)*/)
    6465        {
    6566{/literal}
    6667                {if isset($initial_bounds)}
    67                 var bounds = new google.maps.LatLngBounds( new google.maps.LatLng({$initial_bounds.s},{$initial_bounds.w}), new google.maps.LatLng({$initial_bounds.n},{$initial_bounds.e}) );
    68                 map.setCenter( bounds.getCenter(), map.getBoundsZoomLevel( bounds ) );
     68                mapOptions.iniBounds = new google.maps.LatLngBounds( new google.maps.LatLng({$initial_bounds.s},{$initial_bounds.w}), new google.maps.LatLng({$initial_bounds.n},{$initial_bounds.e}) );
     69                mapOptions.center = mapOptions.iniBounds.getCenter();
     70                {else}
     71                        mapOptions.center = new google.maps.LatLng(0,0);
     72                        mapOptions.zoom = 2;
    6973                {/if}
    7074{literal}
    71                 if ( !map.isLoaded() )
    72                         map.setCenter( new google.maps.LatLng(0,0), 2 );
    73         }
    74 
    75         try {
    76                 // the overview must be added after setting map center
    77                 var ovcontrol = new google.maps.OverviewMapControl(new google.maps.Size(165,165));
    78                 map.addControl(ovcontrol);
    79         }
    80         catch(e){ alert( e.message ); }
    81 
    82         map.enableScrollWheelZoom();
    83         map.enableDoubleClickZoom();
    84 
    85         var pwgMarkerIcon = (function(){ {/literal}{$MAP_MARKER_ICON_JS}{literal} }).call(null);
    86 
    87         map.pwgDataLoader = new PwgDataLoader(map, {rectangle_of_confusion: pwgMarkerIcon.iconSize} );
    88         google.maps.Event.addListener(map.pwgDataLoader, "dataloading", function() {
     75        }
     76
     77        map = new google.maps.Map( document.getElementById("map"), mapOptions );
     78       
     79        if (mapOptions.iniBounds)
     80                map.fitBounds(mapOptions.iniBounds);
     81
     82        if (document.is_debug)
     83        {
     84                google.maps.event.addListener(map, "idle", function() { glog("idle"); });
     85                google.maps.event.addListener(map, "bounds_changed", function() { glog("bounds_changed");} );
     86                google.maps.event.addListener(map, "center_changed", function() { glog("center_changed");} );
     87                google.maps.event.addListener(map, "maptypeid_changed", function() { glog("maptypeid_changed");} );
     88                google.maps.event.addListener(map, "zoom_changed", function() { glog("zoom_changed");} );
     89                google.maps.event.addListener(map, "drag", function() { glog("drag");} );
     90        }
     91
     92        pwgPageLinker = new PageLinker(map, "aLinkToThisPage" );
     93
     94        var pwgMarkerOptions = (function(){ {/literal}{$MAP_MARKER_ICON_JS}{literal} }).call(null);
     95
     96        map.pwgDataLoader = new PwgDataLoader(map, {rectangle_of_confusion: pwgMarkerOptions.roc} );
     97        google.maps.event.addListener(map.pwgDataLoader, "dataloading", function() {
    8998                var pre = '<img src="{/literal}{$PLUGIN_ROOT_URL}{literal}/icons/progress_s.gif" width="16" height="16" alt="~" /> ';
    9099                document.getElementById("dataLoadStatus").innerHTML = pre + Localization.get("Loading");
     
    92101        );
    93102 
    94         google.maps.Event.addListener(map.pwgDataLoader, "dataloadfailed", function(responseCode) {
     103        google.maps.event.addListener(map.pwgDataLoader, "dataloadfailed", function(responseCode) {
    95104                document.getElementById("dataLoadStatus").innerHTML = Localization.get("Failed") + " "+responseCode;
    96105                }
    97106                );
    98107
    99         map.pwgDataHandler = new PwgDataHandler(map, {icon: pwgMarkerIcon, show_all_img_src: "{/literal}{$PLUGIN_ROOT_URL}{literal}/icons/pic_s.gif" } );
    100         google.maps.Event.addListener(map.pwgDataLoader, "dataloaded", pwgBind(map.pwgDataHandler, map.pwgDataHandler.handle) );
     108        map.pwgDataHandler = new PwgDataHandler(map, {icon: pwgMarkerOptions, show_all_img_src: "{/literal}{$PLUGIN_ROOT_URL}{literal}/icons/pic_s.gif" } );
     109        google.maps.event.addListener(map.pwgDataLoader, "dataloaded", pwgBind(map.pwgDataHandler, map.pwgDataHandler.handle) );
    101110
    102111        map.pwgDataLoader.start( "{/literal}{$U_MAP_DATA}{literal}" );
    103 
    104         if (document.is_debug)
    105         {
    106                 google.maps.Event.addListener(map, "moveend", function() { google.maps.Log.write("movend c:"+map.getCenter().toUrlValue());} );
    107                 google.maps.Event.addListener(map, "maptypechanged", function() { google.maps.Log.write("maptypechanged");} );
    108                 google.maps.Event.addListener(map, "infowindowopen", function() { google.maps.Log.write("infowindowopen");} );
    109                 google.maps.Event.addListener(map, "infowindowclose", function() { google.maps.Log.write("infowindowclose");} );
    110         }
    111112}
    112113
     
    118119                !map.pwgDataHandler || map.pwgDataHandler.terminate();
    119120        }
    120         GUnload();
    121121}
    122122
     
    125125        if (!q.length) return false;
    126126        if (!geocoder)
    127                 geocoder = new google.maps.ClientGeocoder();
    128         geocoder.getLocations(q, function(resp)
    129           {
    130                 if ( resp && resp.Status )
     127                geocoder = new google.maps.Geocoder();
     128        geocoder.geocode(q, function(results, status) {
     129                document._geoResponse = results;
     130                if ( status == google.maps.GeocoderStatus.OK )
    131131                {
    132                   document._geoResponse = resp;
    133                   if (resp.Status.code==200)
    134                   {
    135                         var zoom = 2;
    136                         switch (resp.Placemark[0].AddressDetails.Accuracy)
    137                         {
    138                           case 1: zoom=5; break; //country
    139                           case 2: zoom=7; break; //region
    140                           case 3: zoom=8; break; //county
    141                           case 4: zoom=12; break; //town
    142                           case 5: zoom=13; break; //post code
    143                           case 6: zoom=14; break; //street
    144                           case 7: case 8: zoom=16; break; //intersection/exact
    145                         }
    146                         map.setCenter( new google.maps.LatLng( resp.Placemark[0].Point.coordinates[1], resp.Placemark[0].Point.coordinates[0] ), zoom);
    147                   }
    148                   else
    149                         alert("This address has not been found\nCode:"+resp.Status.code);
    150                 }
    151           });
     132                        map.fitBounds( results[0].geometry.viewport );
     133                }
     134          else
     135                        alert("This address has not been found\nCode: "+status);
     136        });
    152137        return false;
    153138}
  • extensions/rv_gmaps/trunk/template/page_linker.js

    r6651 r12698  
    44  this._elementId = aElementId;
    55
    6   PageLinker.url2Map( map );
    7  
    8   google.maps.Event.bind( this._map, "maptypechanged", this, this._regenerateUrl );
    9   google.maps.Event.bind( this._map, "moveend", this, this._regenerateUrl );
    10   if ( this._map.isLoaded() )
    11     this._regenerateUrl();
     6  google.maps.event.bind( this._map, "idle", this, this._regenerateUrl );
     7  google.maps.event.bind( this._map, "maptypeid_changed", this, this._regenerateUrl );
    128}
    139
     
    3228  vars['ll'] = map.getCenter().toUrlValue(5);
    3329  vars['z'] = map.getZoom();
    34   if ( map.getCurrentMapType()===G_NORMAL_MAP )
     30  if ( map.getMapTypeId()===google.maps.MapTypeId.ROADMAP )
    3531    { if (vars['t']) vars['t']=null; }
    3632  else
    37     vars['t']=map.getCurrentMapType().getUrlArg();
     33    vars['t']=map.getMapTypeId();
    3834
    3935  var url = document.location.protocol+'//'+document.location.hostname+document.location.pathname;
     
    5046};
    5147
    52 PageLinker.url2Map = function( map )
     48PageLinker.url2Map = function( mapOptions )
    5349{
    5450  var vars = PageLinker.getQueryVars();
    5551  if ( !( (vars['z'] && vars['ll']) || vars['t'] ) )
    56     return;
     52    return false;
    5753
    58   var mapType = G_NORMAL_MAP;
     54  var mapType = google.maps.MapTypeId.ROADMAP;
    5955  if  (vars['t'])
    6056  for (var i=0; i<map.getMapTypes().length; i++)
    6157    if ( map.getMapTypes()[i].getUrlArg()==vars['t'] )
    6258    {
    63       mapType = map.getMapTypes()[i];break;
     59      mapOptions.mapTypeId = map.getMapTypes()[i];break;
    6460    }
    6561 
    6662  if (vars['z'] && vars['ll'])
    6763  {
    68     var zoom = parseFloat(vars['z']);
     64    mapOptions.zoom = parseFloat(vars['z']);
    6965    var ll = vars['ll'].split( "," );
    7066    if (ll.length==2)
    7167    {
    72       var center = new google.maps.LatLng( ll[0], ll[1] );
    73       map.setCenter( center, zoom, mapType );
     68      mapOptions.center = new google.maps.LatLng( ll[0], ll[1] );
    7469    }
     70    return true;
    7571  }
     72  return false;
    7673}
    7774
Note: See TracChangeset for help on using the changeset viewer.