001/*
002 * This file is part of McIDAS-V
003 *
004 * Copyright 2007-2024
005 * Space Science and Engineering Center (SSEC)
006 * University of Wisconsin - Madison
007 * 1225 W. Dayton Street, Madison, WI 53706, USA
008 * https://www.ssec.wisc.edu/mcidas/
009 * 
010 * All Rights Reserved
011 * 
012 * McIDAS-V is built on Unidata's IDV and SSEC's VisAD libraries, and
013 * some McIDAS-V source code is based on IDV and VisAD source code.  
014 * 
015 * McIDAS-V is free software; you can redistribute it and/or modify
016 * it under the terms of the GNU Lesser Public License as published by
017 * the Free Software Foundation; either version 3 of the License, or
018 * (at your option) any later version.
019 * 
020 * McIDAS-V is distributed in the hope that it will be useful,
021 * but WITHOUT ANY WARRANTY; without even the implied warranty of
022 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
023 * GNU Lesser Public License for more details.
024 * 
025 * You should have received a copy of the GNU Lesser Public License
026 * along with this program.  If not, see https://www.gnu.org/licenses/.
027 */
028
029package edu.wisc.ssec.mcidasv.data.hydra;
030
031import java.rmi.RemoteException;
032
033import org.slf4j.Logger;
034import org.slf4j.LoggerFactory;
035import visad.Set;
036import visad.Gridded1DSet;
037import visad.CoordinateSystem;
038import visad.RealType;
039import visad.RealTupleType;
040import visad.Linear1DSet;
041import visad.Linear2DSet;
042import visad.Gridded2DSet;
043import visad.SampledSet;
044import visad.Unit;
045import visad.FunctionType;
046import visad.VisADException;
047import visad.QuickSort;
048import visad.FlatField;
049import visad.FieldImpl;
050import java.util.HashMap;
051import java.util.Map;
052import visad.GriddedSet;
053
054
055public abstract class ProfileAlongTrack extends MultiDimensionAdapter {
056    
057    private static final Logger logger =
058        LoggerFactory.getLogger(ProfileAlongTrack.class);
059    
060      private FunctionType mathtype;
061
062      int TrackLen;
063      int VertLen;
064
065      private float[] vertLocs = null;
066      private float[] trackTimes = null;
067      private float[] trackLongitude = null;
068      private float[] trackLatitude = null;
069
070      public static String longitude_name = "Longitude";
071      public static String latitude_name  = "Latitude";
072      public static String trackDim_name  = "TrackDim";
073      public static String vertDim_name  = "VertDim";
074      public static String array_name = "array_name";
075      public static String profileTime_name = "ProfileTime";
076      public static String profileTime_unit = "ProfileTime_unit";
077      public static String altitude_unit = "altitude_unit";
078      public static String sfcElev_name = "SurfaceElev";
079      public static String range_name = "range_name";
080      public static String scale_name = "scale_name";
081      public static String offset_name = "offset_name";
082      public static String fill_value_name = "fill_value_name";
083      public static String valid_range = "valid_range";
084      public static String ancillary_file_name = "ancillary_file";
085      static String product_name = "product_name";
086      
087      String[] rangeName_s  = null;
088      Class[] arrayType_s = null;
089      Unit[] rangeUnit_s  = new Unit[] {null};
090
091      RealType track  = RealType.getRealType(trackDim_name);
092      RealType vert = RealType.getRealType(vertDim_name);
093      RealType[] domainRealTypes = new RealType[2];
094
095      RealType vertLocType;
096      RealType trackTimeType;
097
098      int track_idx      = -1;
099      int vert_idx       = -1;
100      int range_rank     = -1;
101
102      int track_tup_idx;
103      int vert_tup_idx;
104
105      boolean isVertDimAlt = true;
106
107      CoordinateSystem cs = null;
108      
109      int medianFilterTrackWidth = 10;
110      int medianFilterVertWidth = 10;
111
112      public static Map<String, double[]> getEmptySubset() {
113        Map<String, double[]> subset = new HashMap<>();
114        subset.put(trackDim_name, new double[3]);
115        subset.put(vertDim_name, new double[3]);
116        return subset;
117      }
118
119      public static Map<String, Object> getEmptyMetadataTable() {
120        Map<String, Object> metadata = new HashMap<>();
121        metadata.put(array_name, null);
122        metadata.put(trackDim_name, null);
123        metadata.put(vertDim_name, null);
124        metadata.put(longitude_name, null);
125        metadata.put(latitude_name, null);
126        metadata.put(profileTime_name, null);
127        metadata.put(profileTime_unit, null);
128        metadata.put(altitude_unit, null);
129        metadata.put(sfcElev_name, null);
130        metadata.put(scale_name, null);
131        metadata.put(offset_name, null);
132        metadata.put(fill_value_name, null);
133        /*
134        metadata.put(range_name, null);
135        metadata.put(range_unit, null);
136        metadata.put(valid_range, null);
137        */
138        return metadata;
139      }
140
141      public ProfileAlongTrack() {
142      }
143
144      public ProfileAlongTrack(MultiDimensionReader reader, Map<String, Object> metadata) {
145        this(reader, metadata, true);
146      }
147
148      public ProfileAlongTrack(MultiDimensionReader reader, Map<String, Object> metadata, boolean isVertDimAlt) {
149        super(reader, metadata);
150        this.isVertDimAlt = isVertDimAlt;
151        this.init();
152      }
153
154
155      private void init() {
156        for (int k=0; k<array_rank;k++) {
157          if ( ((String)metadata.get(trackDim_name)).equals(array_dim_names[k]) ) {
158            track_idx = k;
159          }
160          if ( ((String)metadata.get(vertDim_name)).equals(array_dim_names[k]) ) {
161            vert_idx = k;
162          }
163        }
164
165        int[] lengths = new int[2];
166
167        if (track_idx < vert_idx) {
168          domainRealTypes[0] = vert;
169          domainRealTypes[1] = track;
170          track_tup_idx = 1;
171          vert_tup_idx = 0;
172          lengths[0] = array_dim_lengths[vert_idx];
173          lengths[1] = array_dim_lengths[track_idx];
174        }
175        else {
176          domainRealTypes[0] = track;
177          domainRealTypes[1] = vert;
178          track_tup_idx = 0;
179          vert_tup_idx = 1;
180          lengths[0] = array_dim_lengths[track_idx];
181          lengths[1] = array_dim_lengths[vert_idx];
182        }
183
184        TrackLen = array_dim_lengths[track_idx];
185        VertLen = array_dim_lengths[vert_idx];
186
187        String rangeName = null;
188        if (metadata.get("range_name") != null) {
189          rangeName = (String)metadata.get("range_name");
190        } 
191        else {
192          rangeName = (String)metadata.get("array_name");
193        }
194        rangeType = RealType.getRealType(rangeName, rangeUnit_s[0]);
195
196        try {
197          rangeProcessor = RangeProcessor.createRangeProcessor(reader, metadata);
198        } catch (Exception e) {
199          logger.error("Failed to create RangeProcessor", e);
200        }
201
202        try {
203          if (isVertDimAlt) {
204            vertLocs = getVertBinAltitude();
205          }
206          vertLocType = makeVertLocType();
207          trackTimes = getTrackTimes();
208          trackTimeType = makeTrackTimeType();
209          trackLongitude = getTrackLongitude();
210          trackLatitude = getTrackLatitude();
211        } catch (Exception e) {
212          logger.error("Problem with initializing", e);
213        }
214        
215      }
216
217      public int getTrackLength() {
218        return TrackLen;
219      }
220
221      public int getVertLength() {
222        return VertLen;
223      }
224
225      public int getVertIdx() {
226        return vert_idx;
227      }
228
229      public int getTrackIdx() {
230        return track_idx;
231      }
232
233      public int getVertTupIdx() {
234        return vert_tup_idx;
235      }
236
237      public int getTrackTupIdx() {
238        return track_tup_idx;
239      }
240      
241      public int getMedianFilterWindowWidth() {
242        return medianFilterTrackWidth;
243      }
244      
245      public int getMedianFilterWindowHeight() {
246        return medianFilterVertWidth;
247      }
248                                                                                                                                                     
249      public Set makeDomain(Map<String, double[]> subset) throws Exception {
250        double[] first = new double[2];
251        double[] last = new double[2];
252        int[] length = new int[2];
253
254        Map<String, double[]> domainSubset = new HashMap<>();
255        domainSubset.put(trackDim_name, subset.get(trackDim_name));
256        domainSubset.put(vertDim_name, subset.get(vertDim_name));
257
258        for (int kk=0; kk<2; kk++) {
259          RealType rtype = domainRealTypes[kk];
260          double[] coords = subset.get(rtype.getName());
261          first[kk] = coords[0];
262          last[kk] = coords[1];
263          length[kk] = (int) ((last[kk] - first[kk])/coords[2] + 1);
264          last[kk] = first[kk]+coords[2]*(length[kk]-1);
265        }
266        Linear2DSet domainSet = new Linear2DSet(first[0], last[0], length[0], first[1], last[1], length[1]);
267        final Linear1DSet[] lin1DSet_s = new Linear1DSet[] {domainSet.getLinear1DComponent(0),
268                                           domainSet.getLinear1DComponent(1)};
269
270        float[] new_altitudes = new float[length[vert_tup_idx]];
271        float[] new_times = new float[length[track_tup_idx]];
272
273        int track_start = (int) first[track_tup_idx];
274        int vert_start = (int) first[vert_tup_idx];
275        int vert_skip = (int) (subset.get(vertDim_name))[2];
276        int track_skip = (int) (subset.get(trackDim_name))[2];
277        for (int k=0; k<new_altitudes.length; k++) {
278          new_altitudes[k] = vertLocs[vert_start+(k*vert_skip)];
279        }
280
281        for (int k=0; k<new_times.length; k++) {
282          new_times[k] = trackTimes[track_start+(k*track_skip)];
283        }
284
285        final Gridded1DSet alt_set = new Gridded1DSet(vertLocType, new float[][] {new_altitudes}, new_altitudes.length);
286        final Gridded1DSet time_set = new Gridded1DSet(trackTimeType, new float[][] {new_times}, new_times.length);
287        final float vert_offset = (float) first[vert_tup_idx];
288        final float track_offset = (float) first[track_tup_idx];
289
290        RealTupleType reference = new RealTupleType(vertLocType, trackTimeType);
291        
292        CoordinateSystem cs = null;
293
294        try {
295        cs = new CoordinateSystem(reference, null) {
296          public float[][] toReference(float[][] vals) throws VisADException {
297            int[] indexes = lin1DSet_s[0].valueToIndex(new float[][] {vals[0]});
298            /* ?
299            for (int k=0; k<vals[0].length;k++) {
300               indexes[k] = (int) (vals[vert_tup_idx][k] - vert_offset);
301            }
302            */
303            float[][] alts = alt_set.indexToValue(indexes);
304
305            indexes = lin1DSet_s[1].valueToIndex(new float[][] {vals[1]});
306            /* ?
307            for (int k=0; k<vals[0].length;k++) {
308               indexes[k] = (int) (vals[track_tup_idx][k] - track_offset);
309            }
310            */
311            float[][] times = time_set.indexToValue(indexes);
312
313            return new float[][] {alts[0], times[0]};
314          }
315          public float[][] fromReference(float[][] vals) throws VisADException {
316            int[] indexes = alt_set.valueToIndex(new float[][] {vals[vert_tup_idx]});
317            float[][] vert_coords = lin1DSet_s[vert_tup_idx].indexToValue(indexes);
318            indexes = time_set.valueToIndex(new float[][] {vals[track_tup_idx]});
319            float[][] track_coords = lin1DSet_s[track_tup_idx].indexToValue(indexes);
320            return new float[][] {vert_coords[0], track_coords[0]};
321          }
322          public double[][] toReference(double[][] vals) throws VisADException {
323            return Set.floatToDouble(toReference(Set.doubleToFloat(vals)));
324          }
325          public double[][] fromReference(double[][] vals) throws VisADException {
326            return Set.floatToDouble(fromReference(Set.doubleToFloat(vals)));
327          }
328          public boolean equals(Object obj) {
329            return true;
330          }
331        };
332        }
333        catch (Exception e) {
334        }
335
336        RealTupleType domainTupType = new RealTupleType(domainRealTypes[0], domainRealTypes[1], cs, null);
337        domainSet = new Linear2DSet(domainTupType, first[0], last[0], length[0], first[1], last[1], length[1]);
338
339        return domainSet;
340      }
341
342      public FunctionType getMathType() {
343        return null;
344      }
345
346      public RealType[] getDomainRealTypes() {
347        return domainRealTypes;
348      }
349
350      public Map<String, double[]> getDefaultSubset() {
351        Map<String, double[]> subset = ProfileAlongTrack.getEmptySubset();
352
353        double[] coords = subset.get("TrackDim");
354        coords[0] = 20000.0;
355        coords[1] = (TrackLen - 15000.0) - 1;
356        //-coords[2] = 30.0;
357        coords[2] = 5.0;
358        subset.put("TrackDim", coords);
359
360        coords = subset.get("VertDim");
361        coords[0] = 98.0;
362        coords[1] = (VertLen) - 1;
363        coords[2] = 2.0;
364        subset.put("VertDim", coords);
365        return subset;
366      }
367
368      public int[] getTrackRangeInsideLonLatRect(double minLat, double maxLat, double minLon, double maxLon) {
369        int nn = 100;
370        int skip = TrackLen/nn;
371        double lon;
372        double lat;
373        
374        int idx = 0;
375        while (idx < TrackLen) {
376          lon = (double)trackLongitude[idx];
377          lat = (double)trackLatitude[idx];
378          if (((lon > minLon) && (lon < maxLon)) && ((lat > minLat)&&(lat < maxLat))) break;
379          idx += skip;
380        }
381        if (idx > TrackLen-1) idx = TrackLen-1;
382        if (idx == TrackLen-1) return new int[] {-1,-1};
383
384        int low_idx = idx;
385        while (low_idx > 0) {
386          lon = (double)trackLongitude[low_idx];
387          lat = (double)trackLatitude[low_idx];
388          if (((lon > minLon) && (lon < maxLon)) && ((lat > minLat)&&(lat < maxLat))) {
389            low_idx -= 1;
390            continue;
391          }
392          else {
393            break;
394          }
395        }
396
397        int hi_idx = idx;
398        while (hi_idx < TrackLen-1) {
399          lon = (double)trackLongitude[hi_idx];
400          lat = (double)trackLatitude[hi_idx];
401          if (((lon > minLon) && (lon < maxLon)) && ((lat > minLat)&&(lat < maxLat))) {
402            hi_idx += 1;
403            continue;
404          }
405          else {
406            break;
407          }
408        }
409        return new int[] {low_idx, hi_idx};
410      }
411
412      public Map<String, double[]> getSubsetFromLonLatRect(Map<String, double[]> subset, double minLat, double maxLat, double minLon, double maxLon) {
413        double[] coords = subset.get("TrackDim");
414        int[] idxs = getTrackRangeInsideLonLatRect(minLat, maxLat, minLon, maxLon);
415        coords[0] = (double) idxs[0];
416        coords[1] = (double) idxs[1];
417        if ((coords[0] == -1) || (coords[1] == -1)) return null;
418        return subset;
419      }
420
421      public Map<String, double[]> getSubsetFromLonLatRect(Map<String, double[]> subset, double minLat, double maxLat, double minLon, double maxLon,
422                                             int xStride, int yStride, int zStride) {
423
424        double[] coords = subset.get("TrackDim");
425        int[] idxs = getTrackRangeInsideLonLatRect(minLat, maxLat, minLon, maxLon);
426        coords[0] = (double) idxs[0];
427        coords[1] = (double) idxs[1];
428        if ((coords[0] == -1) || (coords[1] == -1)) return null;
429        if (xStride > 0) {
430          coords[2] = xStride;
431        }
432
433        coords = subset.get("VertDim");
434        if (yStride > 0) {
435          coords[2] = yStride;
436        }
437        return subset;
438      }
439
440      public Map<String, double[]> getSubsetFromLonLatRect(double minLat, double maxLat, double minLon, double maxLon) {
441        return getSubsetFromLonLatRect(getDefaultSubset(), minLat, maxLat, minLon, maxLon);
442      }
443
444      public Map<String, double[]> getSubsetFromLonLatRect(double minLat, double maxLat, double minLon, double maxLon,
445                                             int xStride, int yStride, int zStride) {
446
447        return getSubsetFromLonLatRect(getDefaultSubset(), minLat, maxLat, minLon, maxLon, xStride, yStride, zStride);
448      }
449
450      public abstract float[] getVertBinAltitude() throws Exception;
451      public abstract float[] getTrackTimes() throws Exception;
452      public abstract RealType makeVertLocType() throws Exception;
453      public abstract RealType makeTrackTimeType() throws Exception;
454      public abstract float[] getTrackLongitude() throws Exception;
455      public abstract float[] getTrackLatitude() throws Exception;
456      
457      public static FieldImpl medianFilter(FieldImpl field, int window_lenx, int window_leny) throws VisADException, RemoteException, CloneNotSupportedException  {
458         Set dSet = field.getDomainSet();
459         if (dSet.getManifoldDimension() != 1) {
460            throw new VisADException("medianFilter: outer field domain must have manifoldDimension = 1");
461         }
462         int outerLen = dSet.getLength();
463         
464         FieldImpl filtField = (FieldImpl)field.clone();
465         
466         for (int t=0; t<outerLen; t++) {
467            FlatField ff = (FlatField) filtField.getSample(t, false);
468            medianFilter(ff, window_lenx, window_leny);
469         }
470         
471         return filtField;
472      }
473      
474      /**
475       * Apply a median filter to FlatField range If domain dimension == 3, range is filtered as succession of
476       * 2D filtered layers along the slowest varying dimension.
477       * @param fltFld incoming FlatField to be filtered
478       * @param window_lenx filter window (kernel) dimensions; x: fastest varying dimensions
479       * @param window_leny
480       * @return FlatField with filtered (copied) range
481       * @throws VisADException
482       * @throws RemoteException 
483       */
484      public static FlatField medianFilter(FlatField fltFld, int window_lenx, int window_leny) throws VisADException, RemoteException {
485         GriddedSet domSet = (GriddedSet) fltFld.getDomainSet();
486         FlatField filtFld = new FlatField((FunctionType)fltFld.getType(), domSet);
487         
488         int[] lens = domSet.getLengths();
489         int manifoldDimension = domSet.getManifoldDimension();
490         
491         float[][] rngVals = fltFld.getFloats(false);
492         int rngTupleDim = rngVals.length;
493         float[][] filtVals = new float[rngTupleDim][];
494         
495         if (manifoldDimension == 2) {
496            for (int t=0; t<rngTupleDim; t++) {
497               filtVals[t] = medianFilter(rngVals[t], lens[0], lens[1], window_lenx, window_leny);
498            }
499         }
500         else if (manifoldDimension == 3) {
501            int outerDimLen = lens[0];
502            filtVals = new float[rngTupleDim][lens[0]*lens[1]*lens[2]];
503            float[] rngVals2D = new float[lens[1]*lens[2]];
504            
505            for (int k = 0; k<outerDimLen; k++) {
506               int idx = k*lens[1]*lens[2];
507               for (int t=0; t<rngTupleDim; t++) {
508                  System.arraycopy(rngVals[t], idx, rngVals2D, 0, lens[1]*lens[2]);
509                  
510                  float[] fltA = medianFilter(rngVals2D, lens[1], lens[2], window_lenx, window_leny);
511                  
512                  System.arraycopy(fltA, 0, filtVals[t], idx, lens[1]*lens[2]);
513               }
514            }
515         }
516         
517         filtFld.setSamples(filtVals, false);
518         
519         return filtFld;
520      }
521      
522      /**
523       * Apply median filter to 2D array of values
524       * @param A 2D array to filter
525       * @param lenx Dimensions of A, x varies fastest
526       * @param leny
527       * @param window_lenx Dimensions of window (kernel) x fastest varying
528       * @param window_leny
529       * @return
530       * @throws VisADException 
531       */
532      public static float[] medianFilter(float[] A, int lenx, int leny, int window_lenx, int window_leny)
533           throws VisADException {
534        float[] result =  new float[A.length];
535        float[] window =  new float[window_lenx*window_leny];
536        float[] sortedWindow =  new float[window_lenx*window_leny];
537        int[] sort_indexes = new int[window_lenx*window_leny];
538        int[] indexes = new int[window_lenx*window_leny];
539        int[] indexesB = new int[window_lenx*window_leny];
540        
541        int[] numToInsertAt = new int[window_lenx*window_leny];
542        float[][] valsToInsert = new float[window_lenx*window_leny][window_lenx*window_leny];
543        int[][] idxsToInsert = new int[window_lenx*window_leny][window_lenx*window_leny];
544        
545        int[] numBefore = new int[window_lenx*window_leny];
546        float[][] valsBefore = new float[window_lenx*window_leny][window_lenx*window_leny];
547        int[][] idxsBefore = new int[window_lenx*window_leny][window_lenx*window_leny];
548        
549        int[] numAfter = new int[window_lenx*window_leny];
550        float[][] valsAfter = new float[window_lenx*window_leny][window_lenx*window_leny];
551        int[][] idxsAfter = new int[window_lenx*window_leny][window_lenx*window_leny];                
552        
553        float[] sortedArray = new float[window_lenx*window_leny];
554                                                                                                                                    
555        int a_idx;
556        int w_idx;
557                                                                                                                                    
558        int w_lenx = window_lenx/2;
559        int w_leny = window_leny/2;
560                                                                                                                                    
561        int lo;
562        int hi;
563        int ww_jj;
564        int ww_ii;
565        int cnt=0;
566        int ncnt;
567        int midx;
568        float median;
569        
570        int lenA = A.length;
571        
572        for (int i=0; i<lenx; i++) { // zig-zag better? Maybe, but more complicated
573          for (int j=0; j<leny; j++) {             
574            a_idx = j*lenx + i;
575            
576            if (j > 0) {
577              ncnt = 0;
578              for (int t=0; t<cnt; t++) {
579                 // last window index in data coords: A[lenx,leny]
580                 int k = indexes[sort_indexes[t]];
581                 ww_jj = k/lenx;
582                 ww_ii = k % lenx;
583                 
584                 // current window bounds in data coords
585                 int ww_jj_lo = j - w_leny;
586                 int ww_jj_hi = j + w_leny;
587                 int ww_ii_lo = i - w_lenx;
588                 int ww_ii_hi = i + w_lenx;
589                 
590                 if (ww_jj_lo < 0) ww_jj_lo = 0;
591                 if (ww_ii_lo < 0) ww_ii_lo = 0;
592                 if (ww_jj_hi > leny-1) ww_jj_hi = leny-1;
593                 if (ww_ii_hi > lenx-1) ww_ii_hi = lenx-1;
594                 
595                 
596                 // These are the points which overlap between the last and current window
597                 if ((ww_jj >= ww_jj_lo && ww_jj < ww_jj_hi) && (ww_ii >= ww_ii_lo && ww_ii < ww_ii_hi)) {
598                    window[ncnt] = sortedWindow[t];
599                    indexesB[ncnt] = k;
600                    ncnt++;
601                 }
602              }
603              
604              
605              // Add the new points from sliding the window to the overlap points above
606              java.util.Arrays.fill(numToInsertAt, 0);
607              java.util.Arrays.fill(numBefore, 0);
608              java.util.Arrays.fill(numAfter, 0);
609              
610              ww_jj = w_leny-1 + j;
611              for (int w_i=-w_lenx; w_i<w_lenx; w_i++) {
612                 ww_ii = w_i + i;
613                 int k = ww_jj*lenx+ww_ii;
614                  if (k >= 0 && k < lenA) {
615                     float val = A[k];
616                        if (ncnt > 0) {
617                           int t = 0;
618                           if (val < window[t]) {
619                                 valsBefore[0][numBefore[0]] = val;
620                                 idxsBefore[0][numBefore[0]] = k;
621                                 numBefore[0]++;  
622                                 continue;
623                           }                     
624                           t = ncnt-1;
625                           if (val >= window[t]) {
626                                 valsAfter[0][numAfter[0]] = val;
627                                 idxsAfter[0][numAfter[0]] = k;
628                                 numAfter[0]++;  
629                                 continue;
630                           }
631
632                           for (t=0; t<ncnt-1; t++) {
633                              if (val >= window[t] && val < window[t+1]) {
634                                 valsToInsert[t][numToInsertAt[t]] = val;
635                                 idxsToInsert[t][numToInsertAt[t]] = k;
636                                 numToInsertAt[t]++;
637                                 break;
638                              }
639                           } 
640                        }
641                        else if (!Float.isNaN(val)) {
642                                 valsBefore[0][numBefore[0]] = val;
643                                 idxsBefore[0][numBefore[0]] = k;
644                                 numBefore[0]++;  
645                                 continue;                           
646                        }
647                  }
648              }
649
650              // insert new unsorted values into the already sorted overlap window region
651              int tcnt = 0;
652              
653              for (int it=0; it<numBefore[0]; it++) {
654                 sortedArray[tcnt] = valsBefore[0][it];
655                 indexes[tcnt] = idxsBefore[0][it];
656                 tcnt++;
657              }  
658                       
659              for (int t=0; t<ncnt; t++) {
660                 sortedArray[tcnt] = window[t];
661                 indexes[tcnt] = indexesB[t];
662                 tcnt++;
663                 if (numToInsertAt[t] > 0) {
664                    if (numToInsertAt[t] == 2) { // two item sort here to save work for QuickSort
665                       float val0 = valsToInsert[t][0];
666                       float val1 = valsToInsert[t][1];
667                       
668                       if (val0 <= val1) {
669                          sortedArray[tcnt] = val0;
670                          indexes[tcnt] = idxsToInsert[t][0];
671                          tcnt++;
672                          
673                          sortedArray[tcnt] = val1;
674                          indexes[tcnt] = idxsToInsert[t][1];
675                          tcnt++;
676                       }
677                       else {
678                          sortedArray[tcnt] = val1;
679                          indexes[tcnt] = idxsToInsert[t][1];  
680                          tcnt++;
681                          
682                          sortedArray[tcnt] = val0;
683                          indexes[tcnt] = idxsToInsert[t][0];      
684                          tcnt++;
685                       }
686                    }
687                    else if (numToInsertAt[t] == 1) {
688                       sortedArray[tcnt] = valsToInsert[t][0];
689                       indexes[tcnt] = idxsToInsert[t][0];
690                       tcnt++;
691                    }
692                    else {
693                       for (int it=0; it<numToInsertAt[t]; it++) {
694                          sortedArray[tcnt] = valsToInsert[t][it];
695                          indexes[tcnt] = idxsToInsert[t][it];
696                          tcnt++;
697                       }
698                    }
699                 }
700              }
701              
702              for (int it=0; it<numAfter[0]; it++) {
703                 sortedArray[tcnt] = valsAfter[0][it];
704                 indexes[tcnt] = idxsAfter[0][it];
705                 tcnt++;
706              }  
707              
708              // Now sort the new unsorted and overlap sorted points together to get the new window median
709              
710              System.arraycopy(sortedArray, 0, sortedWindow, 0, tcnt);
711              if (tcnt > 0) {
712                 sort_indexes = QuickSort.sort(sortedWindow, 0, tcnt-1);
713                 median = sortedWindow[tcnt/2];
714              }
715              else {
716                 median = Float.NaN;
717              }
718              cnt = tcnt;
719
720            }
721            else { // full sort done once for each row (see note on zigzag above)
722            
723               cnt = 0;
724               for (int w_j=-w_leny; w_j<w_leny; w_j++) {
725                 for (int w_i=-w_lenx; w_i<w_lenx; w_i++) {
726                   ww_jj = w_j + j;
727                   ww_ii = w_i + i;
728                   w_idx = (w_j+w_leny)*window_lenx + (w_i+w_lenx);
729                   if ((ww_jj >= 0) && (ww_ii >=0) && (ww_jj < leny) && (ww_ii < lenx)) {
730                     int k = ww_jj*lenx+ww_ii;
731                     float val = A[k];
732                     if (!Float.isNaN(val)) {
733                       window[cnt] = val;
734                       indexes[cnt] = k;
735                       cnt++;
736                     }
737                   }
738                 }
739               }
740            
741            
742               System.arraycopy(window, 0, sortedWindow, 0, cnt);
743               if (cnt > 0) {
744                  sort_indexes = QuickSort.sort(sortedWindow, 0, cnt-1);
745                  midx = cnt/2;
746                  median = sortedWindow[midx];
747               }
748               else {
749                  median = Float.NaN;
750               }
751               
752            }
753            
754            if (Float.isNaN(A[a_idx])) {
755              result[a_idx] = Float.NaN;
756            }
757            else {
758              result[a_idx] = median;
759            }
760            
761          }
762        }
763        
764        return result;
765      }
766
767}