blob: 47d00c3aaa502e60cd9ccb26d0e032c548e46399 [file]
# Licensed to the Apache Software Foundation (ASF) under one or more
# contributor license agreements. See the NOTICE file distributed with
# this work for additional information regarding copyright ownership.
# The ASF licenses this file to You under the Apache License, Version 2.0
# (the "License"); you may not use this file except in compliance with
# the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
import json
import os
import pickle
from datetime import datetime, timezone
import mock
import numpy as np
import pytest
import webservice.algorithms_spark.Matchup as matchup
from nexustiles.model.nexusmodel import Tile, TileVariable
from pyspark.sql import SparkSession
from shapely import wkt
from shapely.geometry import box
from webservice.algorithms_spark.Matchup import DomsPoint, Matchup, DataPoint, spark_matchup_driver
class MockSparkParam:
def __init__(self, value):
self.value = value
@pytest.fixture(scope='function')
def test_dir():
test_dir = os.path.dirname(os.path.realpath(__file__))
test_data_dir = os.path.join(test_dir, '..', 'data')
yield test_data_dir
@pytest.fixture(scope='function')
def test_tile():
yield Tile(
tile_id='test-tile',
bbox='',
dataset='test-dataset',
dataset_id='test-dataset',
granule='test-granule',
min_time='2020-07-28T00:00:00',
max_time='2020-07-28T00:00:00',
section_spec='2020-07-28T00:00:00',
meta_data={},
is_multi=True
)
@pytest.fixture(scope='function')
def test_matchup_args():
tile_ids = [1]
polygon_wkt = 'POLYGON((-34.98 29.54, -30.1 29.54, -30.1 31.00, -34.98 31.00, -34.98 29.54))'
primary_ds_name = 'primary-ds-name'
secondary_ds_names = 'test'
parameter = 'sst'
depth_min = 0.0
depth_max = 1.0
time_tolerance = 3.0
radius_tolerance = 1000000.0
platforms = '1,2,3,4,5,6,7,8,9'
yield dict(
tile_ids=tile_ids,
primary_b=MockSparkParam(primary_ds_name),
secondary_b=MockSparkParam(secondary_ds_names),
parameter_b=MockSparkParam(parameter),
tt_b=MockSparkParam(time_tolerance),
rt_b=MockSparkParam(radius_tolerance),
platforms_b=MockSparkParam(platforms),
bounding_wkt_b=MockSparkParam(polygon_wkt),
depth_min_b=MockSparkParam(depth_min),
depth_max_b=MockSparkParam(depth_max)
)
def setup_mock_tile_service(tile):
tile_service_factory = mock.MagicMock()
tile_service = mock.MagicMock()
tile_service_factory.return_value = tile_service
tile_service.get_bounding_box.return_value = box(-90, -45, 90, 45)
tile_service.get_min_time.return_value = 1627490285
tile_service.get_max_time.return_value = 1627490285
tile_service.mask_tiles_to_polygon.return_value = [tile]
tile_service.find_tiles_in_polygon.return_value = [tile]
return tile_service_factory
def test_doms_point_is_pickleable():
edge_point = {
'id': 'argo-profiles-5903995(46, 0)',
'time': '2012-10-15T14:24:04Z',
'longitude': -33.467,
'latitude': 29.728,
'sea_water_temperature': 24.5629997253,
'sea_water_temperature_depth': 2.9796258642,
'wind_speed': None,
'sea_water_salinity': None,
'sea_water_salinity_depth': None,
'device': 3,
'fileurl': 'ftp://podaac-ftp.jpl.nasa.gov/allData/argo-profiles-5903995.nc',
'platform': {
'code': 4
}
}
point = DomsPoint.from_edge_point(edge_point)
assert pickle.dumps(point) is not None
def test_calc(test_matchup_args):
"""
Assert that the expected functions are called during the matchup
calculation and that the results are formatted as expected.
"""
# Mock anything that connects external dependence (Solr, Cassandra, ...)
tile_service_factory = mock.MagicMock()
tile_service = mock.MagicMock()
tile_service_factory.return_value = tile_service
spark = SparkSession.builder.appName('nexus-analysis').getOrCreate()
spark_context = spark.sparkContext
request = mock.MagicMock()
request.get_argument.return_value = '1,2,3,4'
# Patch in request arguments
start_time = datetime.strptime('2020-01-01T00:00:00', '%Y-%m-%dT%H:%M:%S').replace(
tzinfo=timezone.utc)
end_time = datetime.strptime('2020-02-01T00:00:00', '%Y-%m-%dT%H:%M:%S').replace(
tzinfo=timezone.utc)
polygon_wkt = 'POLYGON((-34.98 29.54, -30.1 29.54, -30.1 31.00, -34.98 31.00, -34.98 29.54))'
args = {
'bounding_polygon': wkt.loads(polygon_wkt),
'primary_ds_name': 'primary-ds-name',
'matchup_ds_names': 'matchup-ds-name',
'parameter_s': 'sst',
'start_time': start_time,
'start_seconds_from_epoch': start_time.timestamp(),
'end_time': end_time,
'end_seconds_from_epoch': end_time.timestamp(),
'depth_min': 1.0,
'depth_max': 2.0,
'time_tolerance': 3.0,
'radius_tolerance': 4.0,
'platforms': '1,2,3,4,5,6,7,8,9',
'match_once': True,
'result_size_limit': 10
}
def generate_fake_tile(tile_id):
tile = Tile()
tile.tile_id = tile_id
return tile
# Mock tiles
fake_tiles = [generate_fake_tile(idx) for idx in range(10)]
tile_service.find_tiles_in_polygon.return_value = fake_tiles
# Mock result
# Format of 'spark_result': keys=domspoint,values=list of domspoint
doms_point_args = {
'longitude': -180,
'latitude': -90,
'time': '2020-01-15T00:00:00Z'
}
d1_sat = DomsPoint(**doms_point_args)
d2_sat = DomsPoint(**doms_point_args)
d1_ins = DomsPoint(**doms_point_args)
d2_ins = DomsPoint(**doms_point_args)
d1_sat.data = [DataPoint(
variable_name='sea_surface_temperature',
variable_value=10.0
)]
d2_sat.data = [DataPoint(
variable_name='sea_surface_temperature',
variable_value=20.0
)]
d1_ins.data = [DataPoint(
variable_name='sea_surface_temperature',
variable_value=30.0
)]
d2_ins.data = [DataPoint(
variable_name='sea_surface_temperature',
variable_value=40.0
)]
fake_spark_result = {
d1_sat: [d1_ins, d2_ins],
d2_sat: [d1_ins, d2_ins],
}
matchup_obj = Matchup(tile_service_factory=tile_service_factory, sc=spark_context)
matchup_obj.parse_arguments = lambda _: [item for item in args.values()]
with mock.patch('webservice.algorithms_spark.Matchup.ResultsStorage') as mock_rs, \
mock.patch(
'webservice.algorithms_spark.Matchup.spark_matchup_driver') as mock_matchup_driver:
mock_rs.insertExecution.return_value = 1
mock_matchup_driver.return_value = fake_spark_result
matchup_result = matchup_obj.calc(request)
# Ensure the call to 'spark_matchup_driver' contains the expected params
assert len(mock_matchup_driver.call_args_list) == 1
matchup_driver_args = mock_matchup_driver.call_args_list[0].args
matchup_driver_kwargs = mock_matchup_driver.call_args_list[0].kwargs
assert matchup_driver_args[0] == [tile.tile_id for tile in fake_tiles]
assert wkt.loads(matchup_driver_args[1]).equals(wkt.loads(polygon_wkt))
assert matchup_driver_args[2] == args['primary_ds_name']
assert matchup_driver_args[3] == args['matchup_ds_names']
assert matchup_driver_args[4] == args['parameter_s']
assert matchup_driver_args[5] == args['depth_min']
assert matchup_driver_args[6] == args['depth_max']
assert matchup_driver_args[7] == args['time_tolerance']
assert matchup_driver_args[8] == args['radius_tolerance']
assert matchup_driver_args[9] == args['platforms']
assert matchup_driver_args[10] == args['match_once']
assert matchup_driver_args[11] == tile_service_factory
assert matchup_driver_kwargs['sc'] == spark_context
# Ensure the result of the matchup calculation is as expected
json_matchup_result = json.loads(matchup_result.toJson())
assert len(json_matchup_result['data']) == 2
assert len(json_matchup_result['data'][0]['matches']) == 2
assert len(json_matchup_result['data'][1]['matches']) == 2
for data in json_matchup_result['data']:
assert data['lon'] == '-180'
assert data['lat'] == '-90'
for matches in data['matches']:
assert matches['lon'] == '-180'
assert matches['lat'] == '-90'
assert json_matchup_result['data'][0]['primary'][0]['variable_value'] == 10.0
assert json_matchup_result['data'][1]['primary'][0]['variable_value'] == 20.0
assert json_matchup_result['data'][0]['matches'][0]['secondary'][0]['variable_value'] == 30.0
assert json_matchup_result['data'][0]['matches'][1]['secondary'][0]['variable_value'] == 40.0
assert json_matchup_result['data'][1]['matches'][0]['secondary'][0]['variable_value'] == 30.0
assert json_matchup_result['data'][1]['matches'][1]['secondary'][0]['variable_value'] == 40.0
assert json_matchup_result['details']['numSecondaryMatched'] == 4
assert json_matchup_result['details']['numPrimaryMatched'] == 2
def test_match_satellite_to_insitu(test_dir, test_tile, test_matchup_args):
"""
Test the test_match_satellite_to_insitu and ensure the matchup is
done as expected, where the tile points and in-situ points are all
known and the expected matchup points have been hand-calculated.
This test case mocks out all external dependencies, so Solr,
Cassandra, HTTP insitu requests, etc are all mocked.
The test points are as follows:
X (0, 20) X (20, 20)
O (5, 15)
O (10, 10)
O (18, 3)
X (0, 0) X (20, 0)
The 'X' points are the primary satellite points and the 'O' points
are the secondary satellite or insitu points
Visual inspection reveals that primary point (0, 20) should match
with secondary point (5, 15) and primary point (20, 0) should match
with (18, 3)
"""
test_tile.variables = [TileVariable('sst', 'sea_surface_temperature')]
test_tile.latitudes = np.array([0, 20], dtype=np.float32)
test_tile.longitudes = np.array([0, 20], dtype=np.float32)
test_tile.times = [1627490285]
test_tile.data = np.array([[[11.0, 21.0], [31.0, 41.0]]])
test_tile.get_indices = lambda: [[0, 0, 0], [0, 0, 1], [0, 1, 0], [0, 1, 1]]
test_tile.is_multi = False
tile_ids = [1]
polygon_wkt = 'POLYGON((-34.98 29.54, -30.1 29.54, -30.1 31.00, -34.98 31.00, -34.98 29.54))'
primary_ds_name = 'primary-ds-name'
matchup_ds_names = 'test'
parameter = 'sst'
depth_min = 0.0
depth_max = 1.0
time_tolerance = 3.0
radius_tolerance = 1000000.0
platforms = '1,2,3,4,5,6,7,8,9'
with mock.patch(
'webservice.algorithms_spark.Matchup.edge_endpoints.get_provider_name'
) as mock_edge_endpoints:
# Test the satellite->insitu branch
# By mocking the getEndpointsByName function we are forcing
# Matchup to think this dummy matchup dataset is an insitu
# dataset
mock_edge_endpoints.return_value = 'some-provider'
matchup.query_edge = lambda *args, **kwargs: json.load(
open(os.path.join(test_dir, 'edge_response.json')))
match_args = dict(
tile_ids=tile_ids,
primary_b=MockSparkParam(primary_ds_name),
secondary_b=MockSparkParam(matchup_ds_names),
parameter_b=MockSparkParam(parameter),
tt_b=MockSparkParam(time_tolerance),
rt_b=MockSparkParam(radius_tolerance),
platforms_b=MockSparkParam(platforms),
bounding_wkt_b=MockSparkParam(polygon_wkt),
depth_min_b=MockSparkParam(depth_min),
depth_max_b=MockSparkParam(depth_max),
tile_service_factory=setup_mock_tile_service(test_tile)
)
generator = matchup.match_satellite_to_insitu(**match_args)
def validate_matchup_result(matchup_result, insitu_matchup):
"""
The matchup results for satellite->insitu vs
satellite->satellite are almost exactly the same so they
can be validated using the same logic. They are the same
because they represent the same data, except one test is in
tile format (sat to sat) and one is in edge point format
(insitu). The only difference is the data field is different
for satellite data.
"""
# There should be two primary matchup points
assert len(matchup_result) == 2
# Each primary point matched with 1 matchup point
assert len(matchup_result[0]) == 2
assert len(matchup_result[1]) == 2
# Check that the satellite point was matched to the expected secondary point
assert matchup_result[0][1].latitude == 3.0
assert matchup_result[0][1].longitude == 18.0
assert matchup_result[1][1].latitude == 15.0
assert matchup_result[1][1].longitude == 5.0
# Check that the secondary points have the expected values
if insitu_matchup:
assert matchup_result[0][1].data[0].variable_value == 30.0
assert matchup_result[1][1].data[0].variable_value == 10.0
assert matchup_result[0][1].data[0].variable_name == 'sea_water_temperature'
assert matchup_result[1][1].data[0].variable_name == 'sea_water_temperature'
else:
assert matchup_result[0][1].data[0].variable_value == 30.0
assert matchup_result[1][1].data[0].variable_value == 10.0
assert matchup_result[0][1].data[0].variable_name == 'sst'
assert matchup_result[0][1].data[0].cf_variable_name == 'sea_surface_temperature'
assert matchup_result[1][1].data[0].variable_name == 'sst'
assert matchup_result[1][1].data[0].cf_variable_name == 'sea_surface_temperature'
# Check that the satellite points have the expected values
assert matchup_result[0][0].data[0].variable_value == 21.0
assert matchup_result[1][0].data[0].variable_value == 31.0
assert matchup_result[0][0].data[0].variable_name == 'sst'
assert matchup_result[0][0].data[0].cf_variable_name == 'sea_surface_temperature'
assert matchup_result[1][0].data[0].variable_name == 'sst'
assert matchup_result[1][0].data[0].cf_variable_name == 'sea_surface_temperature'
insitu_matchup_result = list(generator)
validate_matchup_result(insitu_matchup_result, insitu_matchup=True)
# Test the satellite->satellite branch
# By mocking the getEndpointsByName function to return None we
# are forcing Matchup to think this dummy matchup dataset is
# satellite dataset
mock_edge_endpoints.return_value = None
# Open the edge response json. We want to convert these points
# to tile points so we can test sat to sat matchup
edge_json = json.load(open(os.path.join(test_dir, 'edge_response.json')))
points = [wkt.loads(result['point']) for result in edge_json['results']]
matchup_tile = Tile()
matchup_tile.variables = [TileVariable('sst', 'sea_surface_temperature')]
matchup_tile.latitudes = np.array([point.y for point in points], dtype=np.float32)
matchup_tile.longitudes = np.array([point.x for point in points], dtype=np.float32)
matchup_tile.times = [edge_json['results'][0]['time']]
matchup_tile.data = np.array([[[10.0, 0, 0], [0, 20.0, 0], [0, 0, 30.0]]])
matchup_tile.get_indices = lambda: [[0, 0, 0], [0, 1, 1], [0, 2, 2]]
matchup_tile.is_multi = False
match_args['tile_service_factory']().find_tiles_in_polygon.return_value = [matchup_tile]
generator = matchup.match_satellite_to_insitu(**match_args)
sat_matchup_result = list(generator)
validate_matchup_result(sat_matchup_result, insitu_matchup=False)
def test_multi_variable_matchup(test_dir, test_tile, test_matchup_args):
"""
Test multi-variable satellite to in-situ matchup functionality.
"""
test_tile.latitudes = np.array([0, 20], dtype=np.float32)
test_tile.longitudes = np.array([0, 20], dtype=np.float32)
test_tile.times = [1627490285]
test_tile.data = np.array([
[[
[1.10, 2.10],
[3.10, 4.10]
]],
[[
[11.0, 21.0],
[31.0, 41.0]
]]
])
test_tile.is_multi = True
test_tile.variables = [
TileVariable('wind_speed', 'wind_speed'),
TileVariable('wind_dir', 'wind_direction'),
]
test_tile.standard_names = ['', '']
test_matchup_args['tile_service_factory'] = setup_mock_tile_service(test_tile)
with mock.patch(
'webservice.algorithms_spark.Matchup.edge_endpoints.get_provider_name'
) as mock_edge_endpoints:
# Test the satellite->insitu branch
# By mocking the getEndpointsByName function we are forcing
# Matchup to think this dummy matchup dataset is an insitu
# dataset
mock_edge_endpoints.return_value = 'some-provider'
matchup.query_edge = lambda *args, **kwargs: json.load(
open(os.path.join(test_dir, 'edge_response.json')))
generator = matchup.match_satellite_to_insitu(**test_matchup_args)
insitu_matchup_result = list(generator)
tile_var_names = [var.variable_name for var in test_tile.variables]
# wind_speed is first, wind_dir is second
for data_dict in insitu_matchup_result[0][0].data:
assert data_dict.variable_name in tile_var_names
if data_dict.variable_name == 'wind_speed':
assert data_dict.variable_value == 2.10
elif data_dict.variable_name == 'wind_dir':
assert data_dict.variable_value == 21.0
for data_dict in insitu_matchup_result[1][0].data:
assert data_dict.variable_name in tile_var_names
if data_dict.variable_name == 'wind_speed':
assert data_dict.variable_value == 3.10
elif data_dict.variable_name == 'wind_dir':
assert data_dict.variable_value == 31.0
def test_multi_variable_satellite_to_satellite_matchup(test_dir, test_tile, test_matchup_args):
"""
Test multi-variable satellite to satellite matchup functionality.
"""
test_tile.latitudes = np.array([0, 20], dtype=np.float32)
test_tile.longitudes = np.array([0, 20], dtype=np.float32)
test_tile.times = [1627490285]
test_tile.data = np.array([
[[
[1.10, 2.10],
[3.10, 4.10]
]],
[[
[11.0, 21.0],
[31.0, 41.0]
]]
])
test_tile.is_multi = True
test_tile.variables = [
TileVariable('wind_speed', 'wind_speed'),
TileVariable('wind_dir', 'wind_direction')
]
test_matchup_args['tile_service_factory'] = setup_mock_tile_service(test_tile)
with mock.patch(
'webservice.algorithms_spark.Matchup.edge_endpoints.getEndpointByName'
) as mock_edge_endpoints:
mock_edge_endpoints.return_value = None
# Open the edge response json. We want to convert these points
# to tile points so we can test sat to sat matchup
edge_json = json.load(open(os.path.join(test_dir, 'edge_response.json')))
points = [wkt.loads(result['point']) for result in edge_json['results']]
matchup_tile = Tile()
matchup_tile.variables = [
TileVariable('sst', 'sea_surface_temperature'),
TileVariable('wind_dir', 'wind_direction')
]
matchup_tile.latitudes = np.array([point.y for point in points], dtype=np.float32)
matchup_tile.longitudes = np.array([point.x for point in points], dtype=np.float32)
matchup_tile.times = [edge_json['results'][0]['time']]
matchup_tile.data = np.array([
[[
[10.0, 0, 0],
[0, 20.0, 0],
[0, 0, 30.0]
]],
[[
[100.0, 0, 0],
[0, 200.0, 0],
[0, 0, 300.0]
]]
])
# matchup_tile.get_indices = lambda: [[0, 0, 0], [0, 1, 1], [0, 2, 2]]
matchup_tile.is_multi = True
test_matchup_args['tile_service_factory']().find_tiles_in_polygon.return_value = [
matchup_tile
]
generator = matchup.match_satellite_to_insitu(**test_matchup_args)
matchup_result = list(generator)
assert len(matchup_result) == 2
assert len(matchup_result[0]) == 2
assert len(matchup_result[1]) == 2
assert len(matchup_result[0][0].data) == 2
assert len(matchup_result[0][1].data) == 2
assert len(matchup_result[1][0].data) == 2
assert len(matchup_result[1][1].data) == 2
def test_match_once_keep_duplicates():
"""
Ensure duplicate points (in space and time) are maintained when
matchup is called with matchOnce=True. Multiple points with the
same space/time should be kept even if they have different
depth/devices
"""
primary_doms_point = DomsPoint(longitude=1.0, latitude=1.0, time='2017-07-01T00:00:00Z', depth=None, data_id = 'primary')
secondary_doms_point_1 = DomsPoint(longitude=2.0, latitude=2.0, time='2017-07-02T00:00:00Z', depth=-2, data_id = 'secondary1')
secondary_doms_point_2 = DomsPoint(longitude=2.0, latitude=2.0, time='2017-07-02T00:00:00Z', depth=-3, data_id = 'secondary2')
secondary_doms_point_3 = DomsPoint(longitude=100.0, latitude=50.0, time='2017-07-05T00:00:00Z', depth=0, data_id = 'secondary3')
primary_doms_point.data = []
secondary_doms_point_1.data = []
secondary_doms_point_2.data = []
secondary_doms_point_3.data = []
patch_generators = [
(primary_doms_point, secondary_doms_point_3),
(primary_doms_point, secondary_doms_point_2),
(primary_doms_point, secondary_doms_point_1)
]
spark = SparkSession.builder.appName('nexus-analysis').getOrCreate()
spark_context = spark.sparkContext
with mock.patch(
'webservice.algorithms_spark.Matchup.match_satellite_to_insitu',
) as mock_match_satellite_to_insitu, mock.patch(
'webservice.algorithms_spark.Matchup.determine_parallelism'
) as mock_determine_parallelism:
# Mock the actual call to generate a matchup. Hardcode response
# to test this scenario
mock_match_satellite_to_insitu.return_value = patch_generators
mock_determine_parallelism.return_value = 1
match_result = spark_matchup_driver(
tile_ids=['test'],
bounding_wkt='',
primary_ds_name='',
secondary_ds_names='',
parameter='',
depth_min=0,
depth_max=0,
time_tolerance=2000000,
radius_tolerance=0,
platforms='',
match_once=True,
tile_service_factory=lambda x: None,
sc=spark_context
)
assert len(match_result) == 1
secondary_points = match_result[list(match_result.keys())[0]]
assert len(secondary_points) == 2
for point in secondary_points:
assert point.data_id in [secondary_doms_point_1.data_id, secondary_doms_point_2.data_id]
assert point.data_id != secondary_doms_point_3.data_id
def test_prioritize_distance():
"""
Ensure that distance is prioritized over time when prioritizeDistance=True, and
that time is prioritized over distance when prioritizeDistance=False.
"""
primary_doms_point = DomsPoint(longitude=1.0, latitude=1.0, time='2017-07-01T00:00:00Z',
depth=None, data_id='primary')
# Close in space, far in time
secondary_doms_point_1 = DomsPoint(longitude=2.0, latitude=2.0, time='2017-07-08T00:00:00Z',
depth=-1, data_id='secondary1')
# Far in space, close in time
secondary_doms_point_2 = DomsPoint(longitude=90.0, latitude=90.0, time='2017-07-01T00:00:01Z',
depth=-1, data_id='secondary2')
primary_doms_point.data = []
secondary_doms_point_1.data = []
secondary_doms_point_2.data = []
patch_generators = [
(primary_doms_point, secondary_doms_point_1),
(primary_doms_point, secondary_doms_point_2),
]
spark = SparkSession.builder.appName('nexus-analysis').getOrCreate()
spark_context = spark.sparkContext
with mock.patch(
'webservice.algorithms_spark.Matchup.match_satellite_to_insitu',
) as mock_match_satellite_to_insitu, mock.patch(
'webservice.algorithms_spark.Matchup.determine_parallelism'
) as mock_determine_parallelism:
# Mock the actual call to generate a matchup. Hardcode response
# to test this scenario
mock_match_satellite_to_insitu.return_value = patch_generators
mock_determine_parallelism.return_value = 1
match_params = {
'tile_ids': ['test'],
'bounding_wkt': '',
'primary_ds_name': '',
'secondary_ds_names': '',
'parameter': '',
'depth_min': 0,
'depth_max': 0,
'time_tolerance': 2000000,
'radius_tolerance': 0,
'platforms': '',
'match_once': True,
'tile_service_factory': lambda x: None,
'prioritize_distance': True,
'sc': spark_context
}
match_result = spark_matchup_driver(**match_params)
assert len(match_result) == 1
secondary_points = match_result[list(match_result.keys())[0]]
assert len(secondary_points) == 1
assert secondary_points[0].data_id == secondary_doms_point_1.data_id
match_params['prioritize_distance'] = False
match_result = spark_matchup_driver(**match_params)
assert len(match_result) == 1
secondary_points = match_result[list(match_result.keys())[0]]
assert len(secondary_points) == 1
assert secondary_points[0].data_id == secondary_doms_point_2.data_id